Created
February 16, 2017 08:21
-
-
Save chindit/384c9185d21c56a64837b7cf70958324 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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