openstamanager/src/API/App/AppResource.php

315 lines
9.7 KiB
PHP

<?php
/*
* OpenSTAManager: il software gestionale open source per l'assistenza tecnica e la fatturazione
* Copyright (C) DevCode s.r.l.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace API\App;
use API\Interfaces\CreateInterface;
use API\Interfaces\DeleteInterface;
use API\Interfaces\RetrieveInterface;
use API\Interfaces\UpdateInterface;
use API\Resource;
use Carbon\Carbon;
use Illuminate\Support\Collection;
/**
* Risorsa di base per la gestione delle operazioni standard di comunicazione con l'applicazione.
* Implementa le operazioni di *retrieve* in tre fasi, e rende disponibile l'espansione per operazioni di *create*, *update* e *delete*.
*/
abstract class AppResource extends Resource implements RetrieveInterface, CreateInterface, UpdateInterface, DeleteInterface
{
/**
* Gestisce le operazioni di *retrieve* in tre fasi:
* - Cleanup (elenco di record da rimuovere nell'applicazione);
* - Record modificati (elenco di record che sono stati aggiornati nel gestionale);
* - Dettagli del record.
*
* @return array[]
*/
public function retrieve($request)
{
$id = $request['id'];
$last_sync_at = $request['last_sync_at'] && $request['last_sync_at'] != 'undefined' ? new Carbon($request['last_sync_at']) : null;
// Gestione delle operazioni di cleanup
if (strpos($request['resource'], 'cleanup') !== false) {
$list = [];
if (!empty($last_sync_at)) {
$list = $this->getCleanupData($last_sync_at);
}
$list = $this->forceToString($list);
return [
'records' => $list,
];
}
// Gestione dell'enumerazione dei record modificati
if (!isset($id)) {
$list = $this->getModifiedRecords($last_sync_at);
$list = $this->forceToString($list);
return [
'records' => $list,
];
}
// Gestione della visualizzazione dei dettagli del record
$details = $this->retrieveRecord($id);
$details = $this->forceToString($details);
return [
'record' => $details,
];
}
/**
* Gestisce la richiesta di creazione di un record, delegando le operazioni relative a *createRecord* e forzando i risultati in formato stringa.
*
* @return array
*/
public function create($request)
{
$data = $request['data'];
$response_data = $this->createRecord($data);
$response_data = $this->forceToString($response_data);
return [
'id' => $response_data['id'],
'data' => $response_data,
];
}
/**
* Gestisce la richiesta di modifica di un record, delegando le operazioni relative a *updateRecord* e forzando i risultati in formato stringa.
*
* @return array
*/
public function update($request)
{
$data = $request['data'];
$response_data = $this->updateRecord($data);
$response_data = $this->forceToString($response_data);
return [
'data' => $response_data,
];
}
/**
* Gestisce la richiesta di eliminazione di un record, delegando le operazioni relative a *deleteRecord*.
*/
public function delete($request)
{
$id = $request['id'];
$this->deleteRecord($id);
}
/**
* Restituisce un array contenente gli ID dei record eliminati.
*
* @return array
*/
abstract public function getCleanupData($last_sync);
/**
* Restituisce un array contenente gli ID dei record modificati e da sincronizzare.
*
* @param string $last_sync_at
*
* @return array<number, array>
*/
abstract public function getModifiedRecords($last_sync_at);
/**
* Restituisce i dettagli relativi a un singolo record identificato tramite ID.
*
* @param string $id
*
* @return array
*/
abstract public function retrieveRecord($id);
/**
* Crea un nuovo record relativo alla risorsa, restituendo l'ID relativo ed eventuali campi da aggiornare in remoto.
*
* @param array $data
*
* @return array
*/
public function createRecord($data)
{
return [];
}
/**
* Aggiorna un record relativo alla risorsa, restituendo eventuali campi da aggiornare in remoto.
*
* @param array $data
*
* @return array
*/
public function updateRecord($data)
{
return [];
}
/**
* Elimina un record relativo alla risorsa.
*
* @param string $id
*
* @return void
*/
public function deleteRecord($id)
{
}
/**
* Genera la mappatura id => updated_at necessaria per la gestione dei record modificati.
*
* @return array[]
*/
protected function mapModifiedRecords($records)
{
if ($records instanceof Collection) {
return $records->mapToGroups(fn ($item, $key) => [$item['id'] => $item])->toArray();
}
return array_reduce($records, function ($accumulator, $item) {
$accumulator[$item['id']] = $item;
return $accumulator;
}) ?: [];
}
/**
* Converte i valori numerici in stringhe.
*
* @return array
*/
protected function forceToString($list)
{
$result = [];
// Fix per la gestione dei contenuti numerici
foreach ($list as $key => $value) {
if (is_numeric($value)) {
$result[$key] = (string) $value;
} elseif (is_array($value)) {
$result[$key] = $this->forceToString($value);
} else {
$result[$key] = $value;
}
}
return $result;
}
/**
* Restituisce gli ID dei potenziali record mancanti, sulla base della colonna indicata e a partire da *last_sync_at*.
*
* @param string $table_name Tabella da analizzare
* @param string $column Colonna di tipo AUTO_INCREMENT della tabella
* @param null $last_sync_at
*
* @throws \Exception
*
* @return array
*/
protected function getMissingIDs($table_name, $column, $last_sync_at = null)
{
$database = database();
$db_name = $database->getDatabaseName();
// Ottiene il valore successivo della colonna di tipo AUTO_INCREMENT
$database->query('ANALYZE TABLE '.$table_name);
$next_autoincrement = $database->fetchOne('SELECT AUTO_INCREMENT FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = '.prepare($table_name).' AND TABLE_SCHEMA = '.prepare($db_name))['AUTO_INCREMENT'];
/*
// Ottiene l'ultimo record con data precedente a quella impostata
$last_id = null;
if ($last_sync_at) {
$last_record = $database->fetchOne('SELECT '.$column.' AS id FROM '.$table_name.' WHERE created_at <= '.prepare($last_sync_at).' ORDER BY '.$column.' DESC');
$last_id = $last_record['id'];
}
*/
// Ottiene i vuoti all'interno della sequenza AUTO_INCREMENT
$query = 'SELECT (t1.'.$column.' + 1) AS start, (SELECT MIN(t3.'.$column.') - 1 FROM '.$table_name.' t3 WHERE t3.'.$column.' > t1.'.$column.') AS end FROM '.$table_name.' t1 WHERE NOT EXISTS (SELECT t2.'.$column.' FROM '.$table_name.' t2 WHERE t2.'.$column.' = t1.'.$column.' + 1)';
/*
if ($last_id) {
$query .= ' AND t1.'.$column.' >= '.prepare($last_id);
}
*/
$query .= ' ORDER BY start';
$steps = $database->fetchArray($query);
// Gestione dell'eliminazione dei primi record della tabella
$exists_first = $database->fetchNum('SELECT * FROM '.$table_name.' WHERE '.$column.' = 1');
if (!$exists_first) {
$first = $database->fetchOne('SELECT MIN('.$column.') AS min FROM '.$table_name);
$steps[] = [
'start' => 1,
'end' => $first['min'] - 1,
];
}
$total = [];
foreach ($steps as $step) {
if ($step['end'] == null) {
$step['end'] = $next_autoincrement - 1;
}
if ($step['end'] == '-1') {
$step['end'] = $next_autoincrement;
}
if ($step['end'] >= $step['start']) {
$total = array_merge($total, range($step['start'], $step['end']));
}
}
return $total;
}
/**
* Restituisce gli ID dei record con campo *deleted_at* maggiore di *last_sync_at*.
*
* @param string $table_name Tabella da analizzare
* @param string $column Colonna di tipo AUTO_INCREMENT della tabella
* @param null $last_sync_at
*
* @throws \Exception
*
* @return array
*/
protected function getDeleted($table_name, $column, $last_sync_at = null)
{
$query = 'SELECT '.$column.' AS id FROM '.$table_name.' WHERE deleted_at';
if ($last_sync_at) {
$query .= ' > '.prepare($last_sync_at);
} else {
$query .= ' IS NOT NULL';
}
$results = database()->fetchArray($query);
return array_column($results, 'id');
}
}