Files
coolcoth.com/app/Core/Model.php
T
2026-08-08 15:53:53 +08:00

163 lines
5.7 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
namespace Core;
/**
* 模型基类:同时支持 MySQL 与 文件(JSON) 两种存储
* 子类设置 $table 与 $orderBy 即可。
*/
class Model
{
protected $table;
protected $pk = 'id';
protected $orderBy = 'id';
/* ---------- 文件模式 ---------- */
private function file(): string
{
return Db::fileDir() . '/' . $this->table . '.json';
}
private function read(): array
{
$f = $this->file();
if (!is_file($f)) return [];
$d = json_decode(file_get_contents($f), true);
return is_array($d) ? $d : [];
}
private function write(array $rows): void
{
$rows = $this->sanitizeUtf8($rows);
// JSON_INVALID_UTF8_SUBSTITUTE (PHP 7.2+) 保证即使存在非法 UTF-8 也不会让 json_encode 返回 false
// 避免 file_put_contents(false) 把整个数据文件清空为 0 字节(灾难性数据丢失)。
$json = json_encode($rows, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT | JSON_INVALID_UTF8_SUBSTITUTE);
if ($json === false || $json === '') {
error_log('[Model::write] json_encode failed for ' . $this->table . '; aborting write to avoid data loss');
return;
}
$f = $this->file();
$dir = dirname($f);
if (!is_dir($dir)) {
@mkdir($dir, 0755, true);
}
// 写入失败(最常见:服务器目录/文件权限不足,PHP 进程无写权限)必须记录日志,
// 否则会“假成功”——页面跳转回去、用户以为保存了,数据却没变。
$bytes = @file_put_contents($f, $json);
if ($bytes === false) {
$err = error_get_last();
error_log('[Model::write] FAILED to write ' . $f . ' — ' . ($err['message'] ?? 'unknown error') .
' | 请检查目录/文件所有者是否为 PHP 运行用户(宝塔通常是 www)并赋予写权限');
}
}
/** 递归将数组中的字符串修复为合法 UTF-8,剔除非法字节序列 */
private function sanitizeUtf8($v)
{
if (is_array($v)) {
return array_map([$this, 'sanitizeUtf8'], $v);
}
if (is_string($v) && !mb_check_encoding($v, 'UTF-8')) {
return mb_convert_encoding($v, 'UTF-8', 'UTF-8');
}
return $v;
}
private function sort(array &$rows): void
{
$col = $this->orderBy;
usort($rows, function ($a, $b) use ($col) {
$va = $a[$col] ?? 0; $vb = $b[$col] ?? 0;
return $va <=> $vb;
});
}
/* ---------- 通用 CRUD ---------- */
public function all(): array
{
if (Db::driver() === 'mysql') {
return Db::query("SELECT * FROM `{$this->table}` ORDER BY `{$this->orderBy}` ASC")->fetchAll();
}
$rows = $this->read(); $this->sort($rows); return $rows;
}
public function find($id)
{
if (Db::driver() === 'mysql') {
return Db::query("SELECT * FROM `{$this->table}` WHERE `{$this->pk}`=?", [$id])->fetch() ?: null;
}
foreach ($this->read() as $r) if (($r[$this->pk] ?? null) == $id) return $r;
return null;
}
public function where(string $col, $val)
{
if (Db::driver() === 'mysql') {
return Db::query("SELECT * FROM `{$this->table}` WHERE `{$col}`=?", [$val])->fetch() ?: null;
}
foreach ($this->read() as $r) if (($r[$col] ?? null) == $val) return $r;
return null;
}
public function whereAll(string $col, $val): array
{
if (Db::driver() === 'mysql') {
return Db::query("SELECT * FROM `{$this->table}` WHERE `{$col}`=? ORDER BY `{$this->orderBy}` ASC", [$val])->fetchAll();
}
$out = []; foreach ($this->read() as $r) if (($r[$col] ?? null) == $val) $out[] = $r;
$this->sort($out); return $out;
}
public function insert(array $data)
{
if (Db::driver() === 'mysql') {
$cols = array_keys($data);
$sql = "INSERT INTO `{$this->table}` (`" . implode('`,`', $cols) . "`) VALUES (" . implode(',', array_fill(0, count($cols), '?')) . ")";
Db::query($sql, array_values($data));
return Db::pdo()->lastInsertId();
}
$rows = $this->read();
$id = $rows ? (max(array_column($rows, $this->pk)) + 1) : 1;
$data[$this->pk] = $id;
$rows[] = $data; $this->write($rows);
return $id;
}
public function update($id, array $data): void
{
if (Db::driver() === 'mysql') {
$cols = array_keys($data);
$sql = "UPDATE `{$this->table}` SET `" . implode('`=?,`', $cols) . "`=? WHERE `{$this->pk}`=?";
Db::query($sql, array_merge(array_values($data), [$id]));
return;
}
$rows = $this->read();
foreach ($rows as &$r) {
if (($r[$this->pk] ?? null) == $id) { $r = array_merge($r, $data); break; }
}
$this->write($rows);
}
public function delete($id): void
{
if (Db::driver() === 'mysql') {
Db::query("DELETE FROM `{$this->table}` WHERE `{$this->pk}`=?", [$id]);
return;
}
$rows = array_filter($this->read(), fn($r) => ($r[$this->pk] ?? null) != $id);
$this->write(array_values($rows));
}
/** 按任意列批量删除(用于主从表级联删除从表) */
public function deleteRaw(string $col, $val): void
{
if (Db::driver() === 'mysql') {
Db::query("DELETE FROM `{$this->table}` WHERE `{$col}`=?", [$val]);
return;
}
$rows = array_filter($this->read(), fn($r) => ($r[$col] ?? null) != $val);
$this->write(array_values($rows));
}
public function count(): int
{
return count($this->all());
}
}