diff --git a/Controller/SyncController.php b/Controller/SyncController.php index 2989afd..3a3c4de 100755 --- a/Controller/SyncController.php +++ b/Controller/SyncController.php @@ -52,9 +52,11 @@ public function pullAction(Request $request) { $mappings = (isset($data["mappings"])) ? $data["mappings"] : array(); } - $changes = $this->get('nti.sync')->getFromMappings($mappings); + $resultData = $this->get('nti.sync')->getFromMappings($mappings); - return new JsonResponse($changes, 200); + $resultData = json_decode($this->container->get('jms_serializer')->serialize($resultData, 'json'), true); + + return new JsonResponse($resultData, 200); } /** diff --git a/Interfaces/SyncRepositoryInterface.php b/Interfaces/SyncRepositoryInterface.php index 6053bdb..375ced0 100755 --- a/Interfaces/SyncRepositoryInterface.php +++ b/Interfaces/SyncRepositoryInterface.php @@ -2,6 +2,8 @@ namespace NTI\SyncBundle\Interfaces; +use NTI\SyncBundle\Models\SyncPullRequestData; +use NTI\SyncBundle\Models\SyncPullResponseData; use Symfony\Component\DependencyInjection\ContainerInterface; /** @@ -9,25 +11,20 @@ * @package NTI\SyncBundle\Interfaces */ interface SyncRepositoryInterface { + /** - * This function should return a plain array containing the results to be sent to the client + * This function should return an instance of SyncPullResponseData containing the results to be sent to the client * when a sync is requested. The container is also passed as a parameter in order to give additional * flexibility to the repository when making decision on what to show to the client. For example, if the user * making the request only has access to a portion of the data, this can be handled via the container in this method * of the repository. * - * The resulting structure should be the following: - * - * array( - * "data" => (array of objects), - * SyncState::REAL_LAST_TIMESTAMP => (last updated_on date from the array of objects), - * ) + * The resulting structure should be an instance of SyncPullRequestData * * - * @param $timestamp * @param ContainerInterface $container - * @param array $serializationGroups - * @return mixed + * @param SyncPullRequestData $requestData + * @return SyncPullResponseData */ - public function findFromTimestamp($timestamp, ContainerInterface $container, $serializationGroups = array()); + public function findFromTimestamp(ContainerInterface $container, SyncPullRequestData $requestData); } diff --git a/Models/SyncPullRequestData.php b/Models/SyncPullRequestData.php new file mode 100755 index 0000000..afd34e5 --- /dev/null +++ b/Models/SyncPullRequestData.php @@ -0,0 +1,153 @@ + than the timestamp provided + * and all of those 300 results have the same timestamp (for example, it is normal to + * set the inital timestamp to 0 when first installing this bundle) this would cause + * a loop and the client would always sync the same 50 results over and over again. + * + * For this, the client can send the `page` parameter, which then can be used in the repository to offset the results. + * + */ + private $page = 1; + + /** + * @var array|string + * @JMS\Type("array") + * + * The serialization groups that the process should use when returning the results + */ + private $serializationGroups = array("sync_basic"); + + /** + * @return string + */ + public function getMapping() + { + return $this->mapping; + } + + /** + * @param string $mapping + * @return SyncPullRequestData + */ + public function setMapping($mapping) + { + $this->mapping = $mapping; + return $this; + } + + /** + * @return int + */ + public function getTimestamp() + { + return $this->timestamp; + } + + /** + * @param int $timestamp + * @return SyncPullRequestData + */ + public function setTimestamp($timestamp) + { + $this->timestamp = $timestamp; + return $this; + } + + /** + * @return int + */ + public function getLimit() + { + return $this->limit; + } + + /** + * @param int $limit + * @return SyncPullRequestData + */ + public function setLimit($limit) + { + $this->limit = $limit; + return $this; + } + + /** + * @return int + */ + public function getPage() + { + return $this->page; + } + + /** + * @param int $page + * @return SyncPullRequestData + */ + public function setPage($page) + { + $this->page = $page; + return $this; + } + + /** + * @return array|string + */ + public function getSerializationGroups() + { + return $this->serializationGroups; + } + + /** + * @param array|string $serializationGroups + * @return SyncPullRequestData + */ + public function setSerializationGroups($serializationGroups) + { + $this->serializationGroups = $serializationGroups; + return $this; + } + + +} \ No newline at end of file diff --git a/Models/SyncPullResponseData.php b/Models/SyncPullResponseData.php new file mode 100755 index 0000000..6fb45eb --- /dev/null +++ b/Models/SyncPullResponseData.php @@ -0,0 +1,94 @@ +realLastTimestamp; + } + + /** + * @param int $realLastTimestamp + * @return SyncPullResponseData + */ + public function setRealLastTimestamp(int $realLastTimestamp): SyncPullResponseData + { + $this->realLastTimestamp = $realLastTimestamp; + return $this; + } + + /** + * @return array + */ + public function getData(): array + { + return $this->data; + } + + /** + * @param array $data + * @return SyncPullResponseData + */ + public function setData(array $data): SyncPullResponseData + { + $this->data = $data; + return $this; + } + + /** + * @return int + */ + public function getTotalCount(): int + { + return $this->totalCount; + } + + /** + * @param int $totalCount + * @return SyncPullResponseData + */ + public function setTotalCount(int $totalCount): SyncPullResponseData + { + $this->totalCount = $totalCount; + return $this; + } + + +} \ No newline at end of file diff --git a/Repository/SyncRepository.php b/Repository/SyncRepository.php new file mode 100755 index 0000000..675c648 --- /dev/null +++ b/Repository/SyncRepository.php @@ -0,0 +1,64 @@ +getTimestamp(); + $serializationGroups = $requestData->getSerializationGroups(); + $page = $requestData->getPage() > 0 ? $requestData->getPage() - 1 : 0; + $limit = $requestData->getLimit(); + + // Joins + $qb = $this->createQueryBuilder('i'); + $qb->andWhere($qb->expr()->gte('i.lastTimestamp', $timestamp)); + $qb->orderBy('i.lastTimestamp', 'asc'); + + /** + * This should be set BEFORE getting the total count, that way the client will receive + * the actual items that are left for it to sync, not the total amount from a timestamp. + * @Ref the "page"parameter in SyncPulLRequestData + */ + $qb->setFirstResult($page * $limit); + + // Total records + $totalCountQb = clone $qb; + $totalCountQb->select('COUNT(i.id)'); + $totalCountQuery = $totalCountQb->getQuery(); + + try { + $totalCount = intval($totalCountQuery->getSingleScalarResult()); + } catch (\Exception $e) { + $totalCount = 0; + } + + $qb->setMaxResults($limit); + + $items = $qb->getQuery()->getResult(); + + $realLastTimestamp = count($items) <= 0 ? $timestamp : $items[count($items) - 1]->getLastTimestamp(); + + $itemsArray = json_decode($container->get('jms_serializer')->serialize($items, 'json', SerializationContext::create()->setGroups($serializationGroups)), true); + + $result = new SyncPullResponseData(); + $result->setData($itemsArray); + $result->setRealLastTimestamp($realLastTimestamp); + $result->setTotalCount($totalCount); + + return $result; + } + +} \ No newline at end of file diff --git a/Service/SyncService.php b/Service/SyncService.php index bf3542f..d8f8496 100755 --- a/Service/SyncService.php +++ b/Service/SyncService.php @@ -10,6 +10,7 @@ use NTI\SyncBundle\Entity\SyncNewItemState; use NTI\SyncBundle\Entity\SyncState; use NTI\SyncBundle\Interfaces\SyncRepositoryInterface; +use NTI\SyncBundle\Models\SyncPullRequestData; use Symfony\Component\DependencyInjection\ContainerInterface; /** @@ -47,23 +48,17 @@ public function getFromMappings($mappings) { foreach($mappings as $mapping) { - if(!isset($mapping["timestamp"]) || !isset($mapping["mapping"])) { - continue; - } - - $timestamp = $mapping["timestamp"]; - $mappingName = $mapping["mapping"]; - $serializationGroup = (isset($mapping["serializer"])) ? $mapping["serializer"] : "sync_basic"; + $requestData = $this->container->get('jms_serializer')->deserialize(json_encode($mapping), SyncPullRequestData::class, 'json'); - $syncMapping = $this->em->getRepository(SyncMapping::class)->findOneBy(array("name" => $mappingName)); + $syncMapping = $this->em->getRepository(SyncMapping::class)->findOneBy(array("name" => $requestData->getMapping())); if(!$syncMapping) { continue; } - $deletes = $this->em->getRepository(SyncDeleteState::class)->findFromTimestamp($mappingName, $timestamp); - $newItems = $this->em->getRepository(SyncNewItemState::class)->findFromTimestampAndMapping($mappingName, $timestamp); - $failedItems = $this->em->getRepository(SyncFailedItemState::class)->findFromTimestampAndMapping($mappingName, $timestamp); + $deletes = $this->em->getRepository(SyncDeleteState::class)->findFromTimestamp($requestData->getMapping(), $requestData->getTimestamp()); + $newItems = $this->em->getRepository(SyncNewItemState::class)->findFromTimestampAndMapping($requestData->getMapping(), $requestData->getTimestamp()); + $failedItems = $this->em->getRepository(SyncFailedItemState::class)->findFromTimestampAndMapping($requestData->getMapping(), $requestData->getTimestamp()); /** @var SyncRepositoryInterface $repository */ $repository = $this->em->getRepository($syncMapping->getClass()); @@ -72,15 +67,13 @@ public function getFromMappings($mappings) { continue; } - $result = $repository->findFromTimestamp($timestamp, $this->container, $serializationGroup); + $result = $repository->findFromTimestamp($this->container, $requestData); - $changes[$mappingName] = array( - 'changes' => $result["data"], + $changes[$requestData->getMapping()] = array( + 'changes' => $result, 'deletes' => json_decode($this->container->get('jms_serializer')->serialize($deletes, 'json'), true), 'newItems' => json_decode($this->container->get('jms_serializer')->serialize($newItems, 'json'), true), 'failedItems' => json_decode($this->container->get('jms_serializer')->serialize($failedItems, 'json'), true), - SyncState::REAL_LAST_TIMESTAMP => $result[SyncState::REAL_LAST_TIMESTAMP], - SyncState::TOTAL_COUNT => $result[SyncState::TOTAL_COUNT], ); }