OpenXE/classes/Modules/Api/Resource/AbstractResource.php
2021-05-21 08:49:41 +02:00

422 lines
12 KiB
PHP

<?php
namespace Xentral\Modules\Api\Resource;
use Exception;
use InvalidArgumentException;
use Xentral\Components\Database\Database;
use Xentral\Components\Database\SqlQuery\DeleteQuery;
use Xentral\Components\Database\SqlQuery\InsertQuery;
use Xentral\Components\Database\SqlQuery\SelectQuery;
use Xentral\Components\Database\SqlQuery\UpdateQuery;
use Xentral\Modules\Api\Exception\ResourceNotFoundException;
use Xentral\Modules\Api\Resource\Exception\EndpointNotAvailableException;
use Xentral\Modules\Api\Resource\Feature\FilterFeatureTrait;
use Xentral\Modules\Api\Resource\Feature\IncludeFeatureTrait;
use Xentral\Modules\Api\Resource\Feature\SortingFeatureTrait;
use Xentral\Modules\Api\Resource\Feature\ValidationFeatureTrait;
use Xentral\Modules\Api\Resource\Filter\Select\ComplexSearchFilter;
use Xentral\Modules\Api\Resource\Filter\Select\SelectFilterInterface;
use Xentral\Modules\Api\Resource\Filter\Select\SelectFilterTrait;
use Xentral\Modules\Api\Resource\Result\CollectionResult;
use Xentral\Modules\Api\Resource\Result\ItemResult;
use Xentral\Modules\Api\Validator\Validator;
abstract class AbstractResource
{
use SelectFilterTrait;
use FilterFeatureTrait;
use SortingFeatureTrait;
use IncludeFeatureTrait;
use ValidationFeatureTrait;
/** @var Database $db */
protected $db;
/** @var Validator $validator */
protected $validator;
/** @return SelectQuery|false */
abstract protected function selectAllQuery();
/** @return SelectQuery|false */
abstract protected function selectOneQuery();
/** @return SelectQuery|false */
abstract protected function selectIdsQuery();
/** @return InsertQuery|false */
abstract protected function insertQuery();
/** @return UpdateQuery|false */
abstract protected function updateQuery();
/** @return UpdateQuery|DeleteQuery|false */
abstract protected function deleteQuery();
/** @return void */
abstract protected function configure();
/**
* @param Database $database
* @param Validator $validator
*/
public function __construct(
Database $database,
Validator $validator
) {
$this->db = $database;
$this->validator = $validator;
$this->configure();
// Komplexe Suche immer aktivieren
$this->registerSelectFilter(new ComplexSearchFilter());
}
/**
* @param array $filter
* @param array $sorting
* @param array $columns
* @param array $includes
* @param int $page
* @param int $paging
*
* @return CollectionResult
*/
public function getList(
array $filter = [],
array $sorting = [],
array $columns = [],
array $includes = [],
$page = 1,
$paging = 20
) {
/** @var SelectQuery $selectAll */
$selectAll = $this->selectAllQuery();
if (!$selectAll) {
throw new EndpointNotAvailableException();
}
if (!$selectAll instanceof SelectQuery) {
throw new InvalidArgumentException(sprintf(
'selectAllQuery() must return an instance of %s', SelectQuery::class
));
}
// Suchfilter und Sortierung hinzufügen
$selectAll = $this->applySelectFilter($selectAll, [
SelectFilterInterface::TYPE_SEARCHING => $filter,
SelectFilterInterface::TYPE_SORTING => $sorting,
]);
// Filter hinzufügen
//$selectAll = $this->appendFilterQuery($filter, $selectAll);
//$bindValues = $this->appendFilterBindings($filter, $bindValues);
// Sortierung hinzufügen
//$selectAll = $this->appendSorting($sorting, $selectAll);
/*echo "<pre>";
echo $selectAll->getStatement();
var_dump($selectAll->getBindValues());
echo "</pre>";
exit;*/
// Ergebnisse ermitteln
$selectList = clone $selectAll;
if (!empty($columns)) {
$selectList->resetCols()->cols($columns);
}
$selectList->page($page)->setPaging($paging);
$items = $this->db->fetchAll(
$selectList->getStatement(),
$selectList->getBindValues()
);
if (count($items) === 0) {
throw new ResourceNotFoundException();
}
// Gesamtanzahl der Ergebnisse ermitteln
$selectCount = clone $selectAll;
$selectCount->resetOrderBy()->resetCols()->cols(['COUNT(*)']);
$total = (int)$this->db->fetchValue(
$selectCount->getStatement(),
$selectCount->getBindValues()
);
$pagination = $this->getPagination($total, count($items), $paging, $page);
// Includes in Ergebnis integrieren
$items = $this->integrateIncludes($includes, $items);
return new CollectionResult($items, $pagination);
}
/**
* @param array $ids
* @param array $columns Spalten überschreiben
*
* @return CollectionResult
*/
public function getIds(array $ids, array $columns = [])
{
/** @var SelectQuery $selectIds */
$selectIds = $this->selectIdsQuery();
if (!$selectIds) {
throw new EndpointNotAvailableException();
}
if (!$selectIds instanceof SelectQuery) {
throw new InvalidArgumentException(sprintf(
'selectIdsQuery() must return an instance of %s', SelectQuery::class
));
}
if (!empty($columns)) {
$selectIds->resetCols()->cols($columns);
}
$data = $this->db->fetchAssoc(
$selectIds->getStatement(),
['ids' => $ids]
);
if (!$data) {
throw new ResourceNotFoundException();
}
return new CollectionResult($data);
}
/**
* @param int $id
* @param array $includes
*
* @return ItemResult
*/
public function getOne($id, array $includes = [])
{
/** @var SelectQuery $selectOne */
$selectOne = $this->selectOneQuery();
if (!$selectOne) {
throw new EndpointNotAvailableException();
}
if (!$selectOne instanceof SelectQuery) {
throw new InvalidArgumentException(sprintf(
'selectOneQuery() must return an instance of %s', SelectQuery::class
));
}
$data = $this->db->fetchRow($selectOne->getStatement(), ['id' => $id]);
if (!$data) {
throw new ResourceNotFoundException();
}
// Includes in Ergebnis integrieren
$data = $this->integrateIncludes($includes, $data, false);
return new ItemResult($data);
}
/**
* Prüfen ob übergebene ID in Datenbank vorhanden ist
*
* @param int $id
* @param string|null $message Fehlermeldung wenn ID nicht vorhanden ist
*/
public function checkOrFail($id, $message = null)
{
/** @var SelectQuery $selectOne */
$select = $this->selectOneQuery();
if (!$select) {
throw new EndpointNotAvailableException();
}
if (!$select instanceof SelectQuery) {
throw new InvalidArgumentException(sprintf(
'selectOneQuery() must return an instance of %s', SelectQuery::class
));
}
$value = $this->db->fetchValue($select->getStatement(), ['id' => $id]);
if ((int)$value !== (int)$id) {
throw new ResourceNotFoundException($message === null ? 'Resource not found' : $message);
}
}
/**
* @param int $id
* @param array $inputVars
* @param array|null $inputMapping Assoc-Array ['Eingabefeld' => 'Datenbankfeld']
*
* @return ItemResult
*/
public function edit($id, $inputVars, $inputMapping = null)
{
$updateQuery = $this->updateQuery();
if (!$updateQuery) {
throw new EndpointNotAvailableException();
}
if (!$updateQuery instanceof UpdateQuery) {
throw new InvalidArgumentException(sprintf(
'updateQuery() must return an instance of %s', UpdateQuery::class
));
}
// Eingabe validieren
$this->validateData($inputVars, $id);
$inputVars['id'] = $id;
// Eingabe- zu Datenbankfeld mappen
$inputVars = $this->mapInputData($inputVars, $inputMapping);
$bindValues = [];
foreach ($inputVars as $inputKey => $inputVal) {
$updateQuery->col($inputKey);
$bindValues[$inputKey] = $inputVal;
}
$this->db->perform($updateQuery->getStatement(), $bindValues);
// Bei Erfolg die geänderte Resource zurückliefern; mit Success-Flag
$result = $this->getOne($id);
$result->setSuccess(true);
return $result;
}
/**
* @param array $inputVars
* @param array|null $inputMapping Assoc-Array ['Eingabefeld' => 'Datenbankfeld']
*
* @return ItemResult
*/
public function insert($inputVars, $inputMapping = null)
{
$insertQuery = $this->insertQuery();
if (!$insertQuery) {
throw new EndpointNotAvailableException();
}
if (!$insertQuery instanceof InsertQuery) {
throw new InvalidArgumentException(sprintf(
'insertQuery() must return an instance of %s', InsertQuery::class
));
}
// Eingabe validieren
$this->validateData($inputVars);
// Eingabe- zu Datenbankfeld mappen
$inputVars = $this->mapInputData($inputVars, $inputMapping);
$bindValues = [];
foreach ($inputVars as $inputKey => $inputVal) {
$insertQuery->col($inputKey);
$bindValues[$inputKey] = $inputVal;
}
$this->db->perform($insertQuery->getStatement(), $bindValues);
$id = $this->db->lastInsertId();
// Bei Erfolg die angelegte Resource zurückliefern; mit Success-Flag
$result = $this->getOne($id);
$result->setSuccess(true);
return $result;
}
/**
* @param int $id
*
* @return ItemResult
*/
public function delete($id)
{
$deleteQuery = $this->deleteQuery();
if (!$deleteQuery) {
throw new EndpointNotAvailableException();
}
if (!$deleteQuery instanceof DeleteQuery && !$deleteQuery instanceof UpdateQuery) {
throw new InvalidArgumentException(sprintf(
'deleteQuery() must return an instance of %s or %s', DeleteQuery::class, UpdateQuery::class
));
}
try {
$this->db->perform($deleteQuery->getStatement(), ['id' => $id]);
$success = true;
} catch (Exception $e) {
$success = false;
}
$result = new ItemResult(['id' => $id]);
$result->setSuccess($success);
return $result;
}
/**
* Eingabe- zu Datenbankfeld mappen
*
* @param array $inputVars
* @param array|null $inputMapping Assoc-Array ['Eingabefeld' => 'Datenbankfeld']
*
* @return array
*/
protected function mapInputData($inputVars, $inputMapping = null)
{
if (empty($inputMapping)) {
return $inputVars;
}
foreach ($inputMapping as $inputKey => $dbKey) {
if (empty($inputKey) || empty($dbKey)) {
continue;
}
if ($inputKey === $dbKey) {
continue;
}
if (array_key_exists($inputKey, $inputVars)) {
$inputVars[$dbKey] = $inputVars[$inputKey];
unset($inputVars[$inputKey]);
}
}
return $inputVars;
}
/**
* @param int $itemsTotal
* @param int $itemsCurrent
* @param int $itemsPerPage
* @param int $pageCurrent
*
* @return array
*/
protected function getPagination($itemsTotal, $itemsCurrent, $itemsPerPage, $pageCurrent)
{
return [
'items_per_page' => (int)$itemsPerPage,
'items_current' => (int)$itemsCurrent,
'items_total' => (int)$itemsTotal,
'page_current' => (int)$pageCurrent,
'page_last' => (int)ceil($itemsTotal / $itemsPerPage),
];
}
/**
* @param string $resourceClass
*
* @return AbstractResource
*/
protected function getResource($resourceClass)
{
return new $resourceClass(
$this->db,
$this->validator
);
}
}