OneBot: Leantime 改造版源码(BOM/Univer 表格/AI 接管/品牌替换等)
This commit is contained in:
369
app/Core/Db/Repository.php
Normal file
369
app/Core/Db/Repository.php
Normal file
@@ -0,0 +1,369 @@
|
||||
<?php
|
||||
|
||||
namespace Leantime\Core\Db;
|
||||
|
||||
use Illuminate\Contracts\Container\BindingResolutionException;
|
||||
use Leantime\Core\Database;
|
||||
use Leantime\Core\Events\DispatchesEvents;
|
||||
use PDO;
|
||||
use PDOStatement;
|
||||
use ReflectionClass;
|
||||
use ReflectionProperty;
|
||||
|
||||
/**
|
||||
* Repository
|
||||
*/
|
||||
abstract class Repository
|
||||
{
|
||||
use DispatchesEvents;
|
||||
|
||||
protected string $entity;
|
||||
|
||||
protected string $model;
|
||||
|
||||
/**
|
||||
* dbcall - creates a new dbcall object
|
||||
*
|
||||
* @param array $args - usually the value of func_get_args(), gives events/filters values to work with
|
||||
*/
|
||||
protected function dbcall(array ...$args): object
|
||||
{
|
||||
return new class($args, $this)
|
||||
{
|
||||
private ?PDOStatement $stmn = null;
|
||||
|
||||
private array $args;
|
||||
|
||||
private Repository $caller_class;
|
||||
|
||||
/**
|
||||
* @var \Closure|mixed|object|null
|
||||
*/
|
||||
private mixed $db;
|
||||
|
||||
/**
|
||||
* constructor
|
||||
*
|
||||
* @param array $args - usually the value of func_get_args(), gives events/filters values to work with
|
||||
* @param Repository $caller_class - the class object that was called
|
||||
*/
|
||||
public function __construct(array $args, Repository $caller_class)
|
||||
{
|
||||
$this->args = $args;
|
||||
$this->caller_class = $caller_class;
|
||||
// Use the singleton instance of Db to ensure connection pooling
|
||||
$this->db = app()->get(Db::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* prepares sql for entry; wrapper for PDO\prepare()
|
||||
*
|
||||
* @param array $args - additional arguments to pass along to prepare function
|
||||
*/
|
||||
public function prepare(string $sql, array $args = []): void
|
||||
{
|
||||
$sql = $this->caller_class::dispatch_filter(
|
||||
'sql',
|
||||
$sql,
|
||||
$this->getArgs(['prepareArgs' => $args]),
|
||||
4
|
||||
);
|
||||
|
||||
$this->stmn = $this->db->database->prepare($sql, $args);
|
||||
}
|
||||
|
||||
/**
|
||||
* binds values for search/replace of sql; wrapper for PDO\bindValue()
|
||||
*
|
||||
* @param string $needle - placeholder to replace
|
||||
* @param string $replace - value to replace with
|
||||
* @param int $type - type of value being replaced
|
||||
*/
|
||||
public function bindValue(string $needle, mixed $replace, int $type = PDO::PARAM_STR): void
|
||||
{
|
||||
$replace = $this->caller_class::dispatch_filter(
|
||||
'binding.'.str_replace(':', '', $needle),
|
||||
$replace,
|
||||
$this->getArgs(),
|
||||
4
|
||||
);
|
||||
|
||||
$this->stmn->bindValue($needle, $replace, $type);
|
||||
}
|
||||
|
||||
/**
|
||||
* executes the sql call - uses \PDO
|
||||
*/
|
||||
public function lastInsertId(): mixed
|
||||
{
|
||||
return $this->db->database->lastInsertId();
|
||||
}
|
||||
|
||||
/**
|
||||
* executes the sql call - uses \PDO
|
||||
*/
|
||||
public function setFetchMode($mode, $class): bool
|
||||
{
|
||||
return $this->stmn->setFetchMode($mode, $class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the arguments to pass along to events/filter
|
||||
*
|
||||
* @param array $additions - any other additional parameters to include
|
||||
*/
|
||||
private function getArgs(array $additions = []): array
|
||||
{
|
||||
$args = array_merge($this->args, ['self' => $this]);
|
||||
|
||||
if (! empty($additions)) {
|
||||
$args = array_merge($args, $additions);
|
||||
}
|
||||
|
||||
$this->caller_class::dispatch_filter('args', $args, [], 5);
|
||||
|
||||
return $args;
|
||||
}
|
||||
|
||||
/**
|
||||
* executes the sql call - uses \PDO
|
||||
*/
|
||||
public function __call(string $method, $arguments): mixed
|
||||
{
|
||||
if (! isset($this->stmn)) {
|
||||
throw new \Error("You must run the 'prepare' method first!");
|
||||
}
|
||||
|
||||
if (! in_array($method, ['execute', 'fetch', 'fetchAll'])) {
|
||||
throw new \Error('Method does not exist');
|
||||
}
|
||||
|
||||
$this->caller_class::dispatch_event('beforeExecute', $this->getArgs(), 4);
|
||||
|
||||
$this->stmn = $this->caller_class::dispatch_filter('stmn', $this->stmn, $this->getArgs(), 4);
|
||||
$method = $this->caller_class::dispatch_filter('method', $method, $this->getArgs(), 4);
|
||||
|
||||
try {
|
||||
$values = $this->stmn->execute();
|
||||
|
||||
if (in_array($method, ['fetch', 'fetchAll'])) {
|
||||
$values = $this->stmn->$method();
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
// Ensure cursor is closed even on exceptions
|
||||
if (isset($this->stmn)) {
|
||||
$this->stmn->closeCursor();
|
||||
}
|
||||
throw $e;
|
||||
} finally {
|
||||
// Always ensure proper cleanup
|
||||
if (isset($this->stmn)) {
|
||||
$this->stmn->closeCursor();
|
||||
}
|
||||
}
|
||||
|
||||
$this->caller_class::dispatch_event('afterExecute', $this->getArgs(), 4);
|
||||
|
||||
return $this->caller_class::dispatch_filter('return', $values, $this->getArgs(), 4);
|
||||
}
|
||||
|
||||
/**
|
||||
* Destructor to ensure proper cleanup of database resources
|
||||
*/
|
||||
public function __destruct()
|
||||
{
|
||||
if (isset($this->stmn)) {
|
||||
try {
|
||||
$this->stmn->closeCursor();
|
||||
} catch (\Exception $e) {
|
||||
// Silently handle any cleanup errors
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* patch - updates a record in the database
|
||||
*
|
||||
* @param int $id - the id of the record to update
|
||||
* @param array $params - the parameters to update
|
||||
*/
|
||||
public function patch(int $id, array $params): bool
|
||||
{
|
||||
|
||||
unset($params['act']);
|
||||
|
||||
if ($this->entity == '') {
|
||||
report('Patch not implemented for this entity');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$sql = 'UPDATE zp_'.$this->entity.' SET ';
|
||||
|
||||
foreach ($params as $key => $value) {
|
||||
$sql .= ''.Db::sanitizeToColumnString($key).'=:'.Db::sanitizeToColumnString($key).', ';
|
||||
}
|
||||
|
||||
$sql .= 'id=:id WHERE id=:id LIMIT 1';
|
||||
|
||||
$call = $this->dbcall(func_get_args());
|
||||
|
||||
$call->prepare($sql);
|
||||
|
||||
$call->bindValue(':id', $id, PDO::PARAM_STR);
|
||||
|
||||
foreach ($params as $key => $value) {
|
||||
$call->bindValue(':'.Db::sanitizeToColumnString($key), $value, PDO::PARAM_STR);
|
||||
}
|
||||
|
||||
return $call->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \ReflectionException
|
||||
*/
|
||||
public function insert(object $objectToInsert): false|int
|
||||
{
|
||||
|
||||
if ($this->entity == '') {
|
||||
report('Insert not implemented for this entity');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$sql = 'INSERT INTO zp_'.$this->entity.' (';
|
||||
|
||||
$sqlArr = [];
|
||||
foreach ($objectToInsert as $key => $value) {
|
||||
if ($this->getFieldAttribute($objectToInsert, $key)) {
|
||||
$sqlArr[] = '`'.Db::sanitizeToColumnString($key).'`';
|
||||
}
|
||||
}
|
||||
$sql .= implode(',', $sqlArr);
|
||||
|
||||
$sql .= ') VALUES (';
|
||||
|
||||
$sqlArr2 = [];
|
||||
foreach ($objectToInsert as $key => $value) {
|
||||
if ($this->getFieldAttribute($objectToInsert, $key)) {
|
||||
$sqlArr2[] = ':'.Db::sanitizeToColumnString($key).'';
|
||||
}
|
||||
}
|
||||
$sql .= implode(',', $sqlArr2);
|
||||
|
||||
$sql .= ')';
|
||||
|
||||
$call = $this->dbcall(func_get_args());
|
||||
|
||||
$call->prepare($sql);
|
||||
|
||||
foreach ($objectToInsert as $key => $value) {
|
||||
if ($this->getFieldAttribute($objectToInsert, $key)) {
|
||||
$call->bindValue(':'.Db::sanitizeToColumnString($key), $value, PDO::PARAM_STR);
|
||||
}
|
||||
}
|
||||
|
||||
$call->execute();
|
||||
|
||||
return $call->lastInsertId();
|
||||
}
|
||||
|
||||
/**
|
||||
* delete - deletes a record from the database
|
||||
*
|
||||
* @param int $id - the id of the record to delete
|
||||
*/
|
||||
public function delete(int $id): void {}
|
||||
|
||||
/**
|
||||
* get - gets a record from the database
|
||||
*
|
||||
* @param int $id - the id of the record to get
|
||||
*
|
||||
* @throws BindingResolutionException
|
||||
* @throws \ReflectionException
|
||||
*/
|
||||
public function get(int $id): mixed
|
||||
{
|
||||
if ($this->entity == '' || $this->model == '') {
|
||||
report('Get not implemented for this entity');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$sql = 'SELECT ';
|
||||
|
||||
$entityModel = app()->make($this->model);
|
||||
$dbFields = $this->getDbFields($this->model);
|
||||
|
||||
$sql .= implode(',', $dbFields);
|
||||
|
||||
$sql .= ' FROM zp_'.$this->entity.' WHERE id = :id ';
|
||||
|
||||
$call = $this->dbcall(func_get_args());
|
||||
|
||||
$call->prepare($sql);
|
||||
|
||||
$call->bindValue(':id', $id, PDO::PARAM_STR);
|
||||
|
||||
$call->execute();
|
||||
|
||||
$call->setFetchMode(PDO::FETCH_CLASS, $this->model);
|
||||
|
||||
return $call->fetch();
|
||||
}
|
||||
|
||||
/**
|
||||
* getFieldAttribute - gets the field attribute for a given property
|
||||
*
|
||||
* @param object|string $class - the class to get the attribute from
|
||||
* @param string $property - the property to get the attribute from
|
||||
* @param bool $includeId - whether or not to include the id attribute
|
||||
*
|
||||
* @throws \ReflectionException
|
||||
*/
|
||||
protected function getFieldAttribute(object|string $class, string $property, bool $includeId = false): array|false
|
||||
{
|
||||
// Don't create or update id attributes
|
||||
if ($includeId === false && $property == 'id') {
|
||||
return false;
|
||||
}
|
||||
|
||||
$property = new ReflectionProperty($class, $property);
|
||||
|
||||
$attributes = $property->getAttributes();
|
||||
foreach ($attributes as $attribute) {
|
||||
$name = $attribute->getName();
|
||||
if (str_contains($name, 'DbColumn')) {
|
||||
return $attribute->getArguments();
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* getDbFields - gets the database fields for a given class
|
||||
*
|
||||
* @param object|string $class - the class to get the fields from
|
||||
*
|
||||
* @throws \ReflectionException
|
||||
*/
|
||||
protected function getDbFields(object|string $class): array
|
||||
{
|
||||
$property = new ReflectionClass($class);
|
||||
|
||||
$properties = $property->getProperties();
|
||||
|
||||
$propertyArray = [];
|
||||
foreach ($properties as $property) {
|
||||
if ($this->getFieldAttribute($class, $property->getName(), true)) {
|
||||
$propertyArray[] = $property->getName();
|
||||
}
|
||||
}
|
||||
|
||||
return $propertyArray;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user