Skip to content

Instantly share code, notes, and snippets.

@chindit
Created February 16, 2017 08:21
Show Gist options
  • Save chindit/384c9185d21c56a64837b7cf70958324 to your computer and use it in GitHub Desktop.
Save chindit/384c9185d21c56a64837b7cf70958324 to your computer and use it in GitHub Desktop.
<?php
declare(strict_types = 1);
/**
* Created by PhpStorm.
* User: david
* Date: 15/02/2017
* Time: 08:01
*/
namespace AppBundle\Services;
use AppBundle\Entity\Entity;
use Doctrine\ORM\EntityManager;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage;
/**
* Class SyncManager
* @package AppBundle\Services
*/
class SyncManager
{
private $em = null;
private $tokenStorage = null;
private $lastError = "";
private $entity = null;
/**
* SyncManager constructor.
* @param EntityManager $entityManager
* @param TokenStorage $tokenStorage
*/
public function __construct(EntityManager $entityManager, TokenStorage $tokenStorage)
{
$this->em = $entityManager;
$this->tokenStorage = $tokenStorage;
}
/**
* Returns last error
* @return string
*/
public function getLastError() : string
{
return $this->lastError;
}
/**
* Sync data with DB
* @param string $data
* @return array
*/
public function uploadData(string $data) : array
{
$this->lastError = ""; //Re-init last error
$syncedEntities = [];
if (!$this->checkDataFormat($data)) {
return [];
}
$decodedData = json_decode($data, true);
foreach($decodedData as $entity) {
if (!$this->checkSyncStructure($entity) || !$this->hydrate($entity) || !$this->persist($entity)) {
return [];
} else {
$syncedEntities[] = intval($entity['id']);
}
}
return $syncedEntities;
}
/**
* Check if sync data is in correct format
* @param string $data
* @return bool
*/
private function checkDataFormat(string $data) : bool
{
if (!$this->tokenStorage->getToken() || !$this->tokenStorage->getToken()->getUser()) {
$this->lastError = "No user found!";
return false;
}
$decodedData = json_decode($data, true);
if (!$decodedData) {
$this->lastError = "Invalid data received. JSON expected";
return false;
}
if (count($decodedData) == 0 || !is_array($decodedData[0])) {
$this->lastError = "No data to sync";
return false;
}
return true;
}
/**
* Check if main structure is entity-compatible
* @param array $entity
* @return bool
*/
private function checkSyncStructure(array $entity) : bool
{
$expectedStructure = ['id', 'table_name', 'action', 'data'];
foreach($expectedStructure as $structureElement) {
if (!array_key_exists($structureElement, $entity)) {
$this->lastError = "No valid data detected. Sync failed";
return false;
}
}
if (!$this->isEntity($entity['table_name'])) {
$this->lastError = "Entity not found. Sync failed";
return false;
}
$allowedMethods = ['INSERT', 'UPDATE', 'DELETE'];
if (!in_array(strtoupper($entity['action']), $allowedMethods)) {
$this->lastError = "Invalid sync action. Sync failed";
return false;
}
return true;
}
/**
* Hydrate data to matched entity
* @param array $data
* @param bool $isUpdate
* @return bool
*/
private function hydrate(array $data, bool $isUpdate = false) : bool
{
$entityName = 'AppBundle\\Entity\\'.ucfirst($data['table_name']);
if (!$isUpdate) {
$this->entity = new $entityName;
}
foreach ($data['data'] as $key => $value) {
if ($key === 'id') {
continue;
}
$methodName = 'set'.str_replace(' ', '', ucwords(str_replace('_', ' ', $key)));
if (!method_exists($this->entity, $methodName)) {
//LOG! "Property «".$methodName."» doesn't exists!";
$this->lastError = "Invalid entity content. Sync failed";
return false;
}
//Required by «strict_types». We need to cast string to correct value
$entityParser = new \ReflectionMethod($entityName, $methodName);
$parameterType = $entityParser->getParameters()[0]->getType();
if ($this->isEntity(strval($parameterType))) {
try {
$linkedEntity = $this->getEntity(strval($parameterType), $value);
$this->entity->$methodName($linkedEntity);
} catch (\Exception $e) {
$this->lastError = $e->getMessage();
return false;
}
} else {
$converterName = $parameterType.'val';
//Can fail because of strict_types (normally not, value is converted before)
try {
$this->entity->$methodName($converterName($value));
} catch (\Exception $e) {
$this->lastError = "Invalid entity content. Sync failed";
return false;
}
}
}
//In any case, link to current user (which we need to reload to get a Doctrine Entity)
$this->entity->setUser($this->em->getRepository('AppBundle:User')->find($this->tokenStorage->getToken()->getUser()));
return true;
}
/**
* Persist entity to DB
* @param string $data
* @return bool
*/
private function persist(array $data) : bool
{
try {
switch (strtoupper($data['action'])) {
case 'INSERT':
if (!$this->entityExists($data['table_name'], $data['data']['created_at'])) {
$this->em->persist($this->entity);
$this->em->flush();
}
break;
case 'UPDATE':
$this->entity = $this->getEntity($data['table_name'], $data['data']['created_at']);
if (!$this->entity) {
$this->lastError = "Unable to sync data. Cloud is not up-to-date";
return false;
}
$this->hydrate($data, true);
$this->em->flush();
break;
case 'DELETE':
$entity = $this->getEntity($data['table_name'], $data['data']['created_at']);
if (!$entity) {
$this->lastError = "Unable to sync data. Cloud is not up-to-date";
return false;
}
$this->em->remove($entity);
$this->em->flush();
break;
default:
throw new \LogicException("Invalid SQL method detected!");
break;
}
} catch (\Exception $e) {
$this->lastError = $e->getMessage();
return false;
}
return true;
}
/**
* Check if given field is an Entity or not
* @param string $entityName
* @return bool
*/
private function isEntity(string $entityName) : bool
{
return class_exists('AppBundle\\Entity\\'.ucfirst($entityName));
}
/**
* Check if an entity exists in DB
* @param string $entityName
* @param string $entityId
* @return bool
*/
private function entityExists(string $entityName, string $entityId) : bool
{
try {
$this->getEntity($entityName, $entityId);
return true;
} catch (\Exception $e) {
return false;
}
}
/**
* Select linked entity into the DB
* @param string $entityName
* @param string $entityLink
* @return Entity
*/
private function getEntity(string $entityName, string $entityLink) : Entity
{
$entity = $this->em->getRepository('AppBundle:'.ucfirst($entityName))
->findOneBy(['user' => $this->tokenStorage->getToken()->getUser(), 'createdAt' => $entityLink]);
if (!$entity || !$entity instanceof Entity) {
throw new \InvalidArgumentException("Linked entity was not found in DB.");
}
return $entity;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment