Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
679 changes: 332 additions & 347 deletions APISyncExternalModule.php

Large diffs are not rendered by default.

7 changes: 4 additions & 3 deletions cancel-export.php
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
<?php
$module->setExportCancelled(true);
echo 'success';
<?php

$module->setExportCancelled(true);
echo 'success';
10 changes: 5 additions & 5 deletions cancel-sync.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?php
$module->cancelSync();
header('Location: ' . $module->getUrl('api-sync.php'));
<?php

$module->cancelSync();

header('Location: ' . $module->getUrl('api-sync.php'));
150 changes: 76 additions & 74 deletions classes/Batch.php
Original file line number Diff line number Diff line change
@@ -1,74 +1,76 @@
<?php
namespace Vanderbilt\APISyncExternalModule;

use Exception;

class Batch{
private $type;
private $lastLogId;
private $recordIds = [];
private $fields = [];
private $fieldsByRecord = [];

function __construct($type){
$this->type = $type;
}

function getType(){
return $this->type;
}

function getLastLogId(){
return $this->lastLogId;
}

function getRecordIds(){
return array_keys($this->recordIds);
}

function getFields(){
return array_keys($this->fields);
}

function getFieldsByRecord(){
return $this->fieldsByRecord;
}

function add($logId, $recordId, $fields){
if(!is_array($fields)){
throw new Exception('An array must be specified for the fields argument.');
}

if($this->type === APISyncExternalModule::DELETE){
if($fields !== []){
throw new Exception("The fields argument should be an empty array for delete events.");
}
}

if($this->shouldStartNewBatch($fields)){
throw new \Exception("Mixing batches with & without field lists is not allowed! This should have been caught ahead of time in the shouldStartNewBatch() call in BatchBuilder");
}

$this->lastLogId = $logId;
$this->recordIds[$recordId] = true;

foreach($fields as $field){
$this->fields[$field] = true;
$this->fieldsByRecord[$recordId][$field] = true;
}
}

function shouldStartNewBatch($fields){
return
/**
* No reason to start a new batch if this one is empty.
*/
!empty($this->recordIds)
&&
/**
* A batch should either specify fields or not.
* Mixing batches with and without fields can skip data and/or sync a lot more data than necessary.
*/
empty($fields) !== empty($this->fields);
}
}
<?php

namespace Vanderbilt\APISyncExternalModule;

use Exception;

class Batch
{
private $type;
private $lastLogId;
private $recordIds = [];
private $fields = [];
private $fieldsByRecord = [];

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

public function getType() {
return $this->type;
}

public function getLastLogId() {
return $this->lastLogId;
}

public function getRecordIds() {
return array_keys($this->recordIds);
}

public function getFields() {
return array_keys($this->fields);
}

public function getFieldsByRecord() {
return $this->fieldsByRecord;
}

public function add($logId, $recordId, $fields) {
if (!is_array($fields)) {
throw new Exception('An array must be specified for the fields argument.');
}

if ($this->type === APISyncExternalModule::DELETE) {
if ($fields !== []) {
throw new Exception("The fields argument should be an empty array for delete events.");
}
}

if ($this->shouldStartNewBatch($fields)) {
throw new \Exception("Mixing batches with & without field lists is not allowed! This should have been caught ahead of time in the shouldStartNewBatch() call in BatchBuilder");
}

$this->lastLogId = $logId;
$this->recordIds[$recordId] = true;

foreach ($fields as $field) {
$this->fields[$field] = true;
$this->fieldsByRecord[$recordId][$field] = true;
}
}

public function shouldStartNewBatch($fields) {
return
/**
* No reason to start a new batch if this one is empty.
*/
!empty($this->recordIds)
&&
/**
* A batch should either specify fields or not.
* Mixing batches with and without fields can skip data and/or sync a lot more data than necessary.
*/
empty($fields) !== empty($this->fields);
}
}
129 changes: 65 additions & 64 deletions classes/BatchBuilder.php
Original file line number Diff line number Diff line change
@@ -1,64 +1,65 @@
<?php
namespace Vanderbilt\APISyncExternalModule;

require_once __DIR__ . '/Batch.php';

use Exception;

class BatchBuilder {
/** @var Batch[] */
private $batches = [];
private $batchSize;

function __construct($batchSize){
$this->batchSize = $batchSize;
}

function getBatches(){
return $this->batches;
}

function addEvent($logId, $recordId, $event, $fields){
if($event === 'DELETE'){
// If any record fails to delete it will stop the deletion of other records.
// The simplest solution for this was to limit the batch size to one.
// In the future, we could potentially parse failed record IDs out of responses and remove them from batches instead.
$this->startNextBatch(APISyncExternalModule::DELETE);

// The record ID field name will be detected, but specifying fields doesn't make sense when deleting records.
$fields = [];
}
else{
if($this->shouldStartNextBatchBeforeUpdate($fields)){
$this->startNextBatch(APISyncExternalModule::UPDATE);
}
}

$this->addToCurrentBatch($logId, $recordId, $fields);
}

function startNextBatch($type){
$this->batches[] = new Batch($type);
}

function shouldStartNextBatchBeforeUpdate($fields){
$batch = $this->getCurrentBatch();
return
$batch === null // The first batch as not been created yet.
||
count($batch->getRecordIds()) === $this->batchSize
||
$batch->getType() === APISyncExternalModule::DELETE // DELETEs should be in batches by themselves.
||
$batch->shouldStartNewBatch($fields)
;
}

private function addToCurrentBatch($logId, $recordId, $fields){
$this->getCurrentBatch()->add($logId, $recordId, $fields);
}

private function getCurrentBatch(){
return $this->batches[count($this->batches)-1] ?? null;
}
}
<?php

namespace Vanderbilt\APISyncExternalModule;

require_once __DIR__ . '/Batch.php';

use Exception;

class BatchBuilder
{
/** @var Batch[] */
private $batches = [];
private $batchSize;

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

public function getBatches() {
return $this->batches;
}

public function addEvent($logId, $recordId, $event, $fields) {
if ($event === 'DELETE') {
// If any record fails to delete it will stop the deletion of other records.
// The simplest solution for this was to limit the batch size to one.
// In the future, we could potentially parse failed record IDs out of responses and remove them from batches instead.
$this->startNextBatch(APISyncExternalModule::DELETE);

// The record ID field name will be detected, but specifying fields doesn't make sense when deleting records.
$fields = [];
} else {
if ($this->shouldStartNextBatchBeforeUpdate($fields)) {
$this->startNextBatch(APISyncExternalModule::UPDATE);
}
}

$this->addToCurrentBatch($logId, $recordId, $fields);
}

public function startNextBatch($type) {
$this->batches[] = new Batch($type);
}

public function shouldStartNextBatchBeforeUpdate($fields) {
$batch = $this->getCurrentBatch();
return
$batch === null // The first batch as not been created yet.
||
count($batch->getRecordIds()) === $this->batchSize
||
$batch->getType() === APISyncExternalModule::DELETE // DELETEs should be in batches by themselves.
||
$batch->shouldStartNewBatch($fields)
;
}

private function addToCurrentBatch($logId, $recordId, $fields) {
$this->getCurrentBatch()->add($logId, $recordId, $fields);
}

private function getCurrentBatch() {
return $this->batches[count($this->batches) - 1] ?? null;
}
}
Loading