first commit
This commit is contained in:
@@ -0,0 +1,312 @@
|
||||
<?php
|
||||
/**
|
||||
* @file classes/db/SchemaDAO.php
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2000-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class SchemaDAO
|
||||
*
|
||||
* @ingroup db
|
||||
*
|
||||
* @brief A base class for DAOs which rely on a json-schema file to define
|
||||
* the data object.
|
||||
*/
|
||||
|
||||
namespace PKP\db;
|
||||
|
||||
use APP\core\Services;
|
||||
use Exception;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
/**
|
||||
* @template T of \PKP\core\DataObject
|
||||
*/
|
||||
abstract class SchemaDAO extends DAO
|
||||
{
|
||||
/** @var string One of the SCHEMA_... constants */
|
||||
public $schemaName;
|
||||
|
||||
/** @var string The name of the primary table for this object */
|
||||
public $tableName;
|
||||
|
||||
/** @var string The name of the settings table for this object */
|
||||
public $settingsTableName;
|
||||
|
||||
/** @var string The column name for the object id in primary and settings tables */
|
||||
public $primaryKeyColumn;
|
||||
|
||||
/** @var array Maps schema properties for the primary table to their column names */
|
||||
public $primaryTableColumns = [];
|
||||
|
||||
/**
|
||||
* Create a new \PKP\core\DataObject of the appropriate class
|
||||
*
|
||||
* @return T
|
||||
*/
|
||||
abstract public function newDataObject();
|
||||
|
||||
/**
|
||||
* Retrieve an object by ID
|
||||
*
|
||||
* @param int $objectId
|
||||
*
|
||||
* @return ?T
|
||||
*/
|
||||
public function getById($objectId)
|
||||
{
|
||||
$result = $this->retrieve(
|
||||
'SELECT * FROM ' . $this->tableName . ' WHERE ' . $this->primaryKeyColumn . ' = ?',
|
||||
[(int) $objectId]
|
||||
);
|
||||
|
||||
$row = (array) $result->current();
|
||||
return $row ? $this->_fromRow($row) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert a new object
|
||||
*
|
||||
* @param T $object The object to insert into the database
|
||||
*
|
||||
* @return int The new object's id
|
||||
*/
|
||||
public function insertObject($object)
|
||||
{
|
||||
$schemaService = Services::get('schema');
|
||||
$schema = $schemaService->get($this->schemaName);
|
||||
$sanitizedProps = $schemaService->sanitize($this->schemaName, $object->_data);
|
||||
|
||||
$primaryDbProps = $this->_getPrimaryDbProps($object);
|
||||
|
||||
if (empty($primaryDbProps)) {
|
||||
throw new Exception('Tried to insert ' . get_class($object) . ' without any properties for the ' . $this->tableName . ' table.');
|
||||
}
|
||||
|
||||
DB::table($this->tableName)->insert($primaryDbProps);
|
||||
$object->setId(DB::getPdo()->lastInsertId());
|
||||
|
||||
// Add additional properties to settings table if they exist
|
||||
if (count($sanitizedProps) !== count($primaryDbProps)) {
|
||||
$columns = [$this->primaryKeyColumn, 'locale', 'setting_name', 'setting_value'];
|
||||
$columnsList = join(', ', $columns);
|
||||
$bindList = join(', ', array_fill(0, count($columns), '?'));
|
||||
foreach ($schema->properties as $propName => $propSchema) {
|
||||
if (!isset($sanitizedProps[$propName]) || array_key_exists($propName, $this->primaryTableColumns)) {
|
||||
continue;
|
||||
}
|
||||
if (!empty($propSchema->multilingual)) {
|
||||
foreach ($sanitizedProps[$propName] as $localeKey => $localeValue) {
|
||||
$this->update("INSERT INTO {$this->settingsTableName} ({$columnsList}) VALUES ({$bindList})", [
|
||||
$object->getId(),
|
||||
$localeKey,
|
||||
$propName,
|
||||
$this->convertToDB($localeValue, $schema->properties->{$propName}->type),
|
||||
]);
|
||||
}
|
||||
} else {
|
||||
$this->update("INSERT INTO {$this->settingsTableName} ({$columnsList}) VALUES ({$bindList})", [
|
||||
$object->getId(),
|
||||
'',
|
||||
$propName,
|
||||
$this->convertToDB($sanitizedProps[$propName], $schema->properties->{$propName}->type),
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $object->getId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update an object
|
||||
*
|
||||
* When updating an object, we remove table rows for any settings which are
|
||||
* no longer present.
|
||||
*
|
||||
* Multilingual fields are an exception to this. When a locale key is missing
|
||||
* from the object, it is not removed from the database. This is to ensure
|
||||
* that data from a locale which is later disabled is not automatically
|
||||
* removed from the database, and will appear if the locale is re-enabled.
|
||||
*
|
||||
* To delete a value for a locale key, a null value must be passed.
|
||||
*
|
||||
* @param T $object The object to insert into the database
|
||||
*/
|
||||
public function updateObject($object)
|
||||
{
|
||||
$schemaService = Services::get('schema');
|
||||
$schema = $schemaService->get($this->schemaName);
|
||||
$sanitizedProps = $schemaService->sanitize($this->schemaName, $object->_data);
|
||||
|
||||
$primaryDbProps = $this->_getPrimaryDbProps($object);
|
||||
|
||||
$set = join('=?,', array_keys($primaryDbProps)) . '=?';
|
||||
$this->update(
|
||||
"UPDATE {$this->tableName} SET {$set} WHERE {$this->primaryKeyColumn} = ?",
|
||||
array_merge(array_values($primaryDbProps), [$object->getId()])
|
||||
);
|
||||
|
||||
$deleteSettings = [];
|
||||
foreach ($schema->properties as $propName => $propSchema) {
|
||||
if (array_key_exists($propName, $this->primaryTableColumns)) {
|
||||
continue;
|
||||
} elseif (!isset($sanitizedProps[$propName])) {
|
||||
$deleteSettings[] = $propName;
|
||||
continue;
|
||||
}
|
||||
if (!empty($propSchema->multilingual)) {
|
||||
foreach ($sanitizedProps[$propName] as $localeKey => $localeValue) {
|
||||
// Delete rows with a null value
|
||||
if (is_null($localeValue)) {
|
||||
$this->update("DELETE FROM {$this->settingsTableName} WHERE {$this->primaryKeyColumn} = ? AND setting_name = ? AND locale = ?", [
|
||||
$object->getId(),
|
||||
$propName,
|
||||
$localeKey,
|
||||
]);
|
||||
} else {
|
||||
DB::table($this->settingsTableName)->updateOrInsert(
|
||||
[$this->primaryKeyColumn => $object->getId(), 'locale' => $localeKey, 'setting_name' => $propName],
|
||||
['setting_value' => $this->convertToDB($localeValue, $schema->properties->{$propName}->type)]
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
DB::table($this->settingsTableName)->updateOrInsert(
|
||||
[$this->primaryKeyColumn => $object->getId(), 'locale' => '', 'setting_name' => $propName],
|
||||
['setting_value' => $this->convertToDB($sanitizedProps[$propName], $schema->properties->{$propName}->type)]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (count($deleteSettings)) {
|
||||
$deleteSettingNames = join(',', array_map(function ($settingName) {
|
||||
return "'{$settingName}'";
|
||||
}, $deleteSettings));
|
||||
$this->update(
|
||||
"DELETE FROM {$this->settingsTableName} WHERE {$this->primaryKeyColumn} = ? AND setting_name in ({$deleteSettingNames})",
|
||||
[$object->getId()]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete an object
|
||||
*
|
||||
* A wrapper function for SchemaDAO::deleteObjectById().
|
||||
*
|
||||
* @param T $object The object to insert into the database
|
||||
*/
|
||||
public function deleteObject($object)
|
||||
{
|
||||
$this->deleteById($object->getId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete an object by its ID
|
||||
*
|
||||
* @param int $objectId
|
||||
*/
|
||||
public function deleteById($objectId)
|
||||
{
|
||||
$this->update(
|
||||
"DELETE FROM {$this->tableName} WHERE {$this->primaryKeyColumn} = ?",
|
||||
[(int) $objectId]
|
||||
);
|
||||
$this->update(
|
||||
"DELETE FROM {$this->settingsTableName} WHERE {$this->primaryKeyColumn} = ?",
|
||||
[(int) $objectId]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a \PKP\core\DataObject from a result row
|
||||
*
|
||||
* @param array $primaryRow The result row from the primary table lookup
|
||||
*
|
||||
* @return T
|
||||
*/
|
||||
public function _fromRow($primaryRow)
|
||||
{
|
||||
$schemaService = Services::get('schema');
|
||||
$schema = $schemaService->get($this->schemaName);
|
||||
|
||||
$object = $this->newDataObject();
|
||||
|
||||
foreach ($this->primaryTableColumns as $propName => $column) {
|
||||
if (isset($primaryRow[$column])) {
|
||||
$object->setData(
|
||||
$propName,
|
||||
$this->convertFromDb($primaryRow[$column], $schema->properties->{$propName}->type)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$result = $this->retrieve(
|
||||
"SELECT * FROM {$this->settingsTableName} WHERE {$this->primaryKeyColumn} = ?",
|
||||
[$primaryRow[$this->primaryKeyColumn]]
|
||||
);
|
||||
|
||||
foreach ($result as $settingRow) {
|
||||
$settingRow = (array) $settingRow;
|
||||
if (!empty($schema->properties->{$settingRow['setting_name']})) {
|
||||
$object->setData(
|
||||
$settingRow['setting_name'],
|
||||
$this->convertFromDB(
|
||||
$settingRow['setting_value'],
|
||||
$schema->properties->{$settingRow['setting_name']}->type
|
||||
),
|
||||
empty($settingRow['locale']) ? null : $settingRow['locale']
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $object;
|
||||
}
|
||||
|
||||
/**
|
||||
* A helper function to compile the key/value set for the primary table
|
||||
*
|
||||
* @param T $object
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function _getPrimaryDbProps($object)
|
||||
{
|
||||
$schema = Services::get('schema')->get($this->schemaName);
|
||||
$sanitizedProps = Services::get('schema')->sanitize($this->schemaName, $object->_data);
|
||||
|
||||
$primaryDbProps = [];
|
||||
foreach ($this->primaryTableColumns as $propName => $columnName) {
|
||||
if ($propName !== 'id' && array_key_exists($propName, $sanitizedProps)) {
|
||||
// If the value is null and the prop is nullable, leave it null
|
||||
if (is_null($sanitizedProps[$propName])
|
||||
&& isset($schema->properties->{$propName}->validation)
|
||||
&& in_array('nullable', $schema->properties->{$propName}->validation)) {
|
||||
$primaryDbProps[$columnName] = null;
|
||||
|
||||
// Convert empty string values for DATETIME columns into null values
|
||||
// because an empty string can not be saved to a DATETIME column
|
||||
} elseif (array_key_exists($columnName, $sanitizedProps)
|
||||
&& $sanitizedProps[$columnName] === ''
|
||||
&& isset($schema->properties->{$propName}->validation)
|
||||
&& (
|
||||
in_array('date_format:Y-m-d H:i:s', $schema->properties->{$propName}->validation)
|
||||
|| in_array('date_format:Y-m-d', $schema->properties->{$propName}->validation)
|
||||
)
|
||||
) {
|
||||
$primaryDbProps[$columnName] = null;
|
||||
} else {
|
||||
$primaryDbProps[$columnName] = $this->convertToDB($sanitizedProps[$propName], $schema->properties->{$propName}->type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $primaryDbProps;
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\db\SchemaDAO', '\SchemaDAO');
|
||||
}
|
||||
Reference in New Issue
Block a user