Model/src/ArrayInstance.php

121 lines
3.8 KiB
PHP
Raw Normal View History

2024-11-08 17:12:46 +01:00
<?php
namespace Nest\Model;
use Nest\Database\Query\DeleteQuery;
use Nest\Database\Query\InsertQuery;
use Nest\Database\Query\Raw;
use Nest\Database\Query\SelectQuery;
use Nest\Database\Exceptions\NotCurrentTransactionException;
use Nest\Database\Exceptions\Query\MissingConditionValueException;
use Nest\Database\Exceptions\UnknownDatabaseException;
use Nest\Model\Query\EntityQuery;
use function Nest\Utils\array_first_or_val;
use function Nest\Utils\str_snake_singularize;
class ArrayInstance extends ReadableArrayBlueprint
{
/**
* Create an array field instance from an array field definition.
* @param ArrayBlueprint $array
*/
public function __construct(ArrayBlueprint $array)
{
parent::__construct($array->entityClass, $array->name);
// Copy all fields.
$this->type = $array->type;
$this->table = $array->table;
$this->foreignKeyName = $array->foreignKeyName;
$this->foreignValueName = $array->foreignValueName;
}
/**
* Get current table / foreign key / foreign value state.
* @param string $fromTable Table from which to get values.
* @return array{string, string, string} Table, Foreign key, Foreign value.
*/
public function getState(string $fromTable): array
{
return [
$table = $this->getTable() ?? "{$fromTable}_{$this->name}",
$foreignKey = $this->getForeignKeyName() ?? "$table.".str_snake_singularize($fromTable)."_id",
$foreignValue = $this->getForeignValueName() ?? "$table.".str_snake_singularize($this->name),
];
}
/**
* Extract a column name from a fully qualified name.
* @return string The column name.
*/
private function getColumnName(string $fullyQualifiedName): string
{
return substr($fullyQualifiedName, strrpos($fullyQualifiedName, ".") + 1);
}
/**
* Setup inline loading for the current array field in an entity query.
* @param EntityQuery $baseQuery The entity query to alter.
* @param string $prefix Prefix to add to the name of the properties.
* @return Raw Generated select subquery.
* @throws MissingConditionValueException
*/
public function genSelect(EntityQuery $baseQuery, string $prefix = ""): Raw
{
// Get all auto values for undefined tables or keys.
[$table, $foreignKey, $foreignValue] = $this->getState($baseQuery->getTableName());
// Build subquery SELECT SQL.
$sql = ((new SelectQuery($baseQuery->getDatabase(), $table))
->select($foreignValue)
->whereColumn("{$baseQuery->getTableName()}.".$baseQuery->getPrimaryKeyName(), "=", $foreignKey))->build();
// Return the raw SELECT of a JSON array.
return new Raw(
"ARRAY_TO_JSON(ARRAY($sql->sql)) AS $prefix$this->name",
$sql->bindings,
);
}
/**
* @param Entity $entity Entity for which to save.
* @param array $value Array to save.
* @return void
* @throws MissingConditionValueException
* @throws UnknownDatabaseException
* @throws NotCurrentTransactionException
*/
public function save(Entity $entity, array $value): void
{
// Start a transaction for the whole save.
$transaction = $entity->getDatabase()->startTransaction();
// Get all auto values for undefined tables or keys.
[$table, $foreignKey, $foreignValue] = $this->getState($entity->getTableName());
// Get entity primary key value.
$entityKey = $entity->{array_first_or_val($entity->getPrimaryFields())};
// Delete existing values.
((new DeleteQuery($entity->getDatabase(), $table))
->where($foreignKey, "=", $entityKey))
->execute();
// Get actual columns names.
$foreignKey = $this->getColumnName($foreignKey);
$foreignValue = $this->getColumnName($foreignValue);
// Insert new ones.
((new InsertQuery($entity->getDatabase(), $table)))
// Insert all values of the array.
->values(...array_map(fn (mixed $val) => ([
$foreignKey => $entityKey,
$foreignValue => $val,
]), $value))
->execute();
// Commit transaction.
$transaction->commit();
}
}