<?php

namespace xmvc;

use Exception;
use Symfony\Component\Yaml\Yaml;
use xmvc\event\MetaDeleteEvent;
use xmvc\event\MetaInsertEvent;
use xmvc\event\MetaUpdateEvent;

/**
 * Meta 基类
 *
 * @property DB $metaDb 数据库类
 * @event onGet():mixed 取值事件, 参数['name'=> string(元对象类名), meta' => $this, 'name' => string, 'value' => mixed]
 * @event onSet():mixed 设置值事件, 返回值替换设置值, 参数['name'=> string(元对象类名),'meta' => $this, 'name' => string, 'value' => mixed]
 * @event onSaveBefore 保存之前事件, 返回 false 则中断保存 参数['name'=> string(元对象类名),'meta' => $this, 'db' => DB, 'q' => Sqler, 'isNewCreate' => bool(是否是新记录)]
 * @event onSaveAfter 保存之后事件,  参数['name'=> string(元对象类名),'meta' => $this, 'db' => DB, 'q' => Sqler, 'isNewCreate' => bool(是否是新记录), 'autoId' => int(自增ID属性的值一般是主键ID)]
 * @event onDeleteBefore(]):bool 删除之前事件, 返回 false 则中断删除, 参数['name'=> string(元对象类名),'meta' => $this, 'db' => DB, 'q' => Sqler]
 * @event onDeleteAfter():void 删除后事件,  参数['name'=> string(元对象类名), 'meta' => $this, 'db' => DB, 'q' => Sqler, 'rownums' => int(受影响行数)]
 */
abstract class BaseMeta
{
    /** 元对象完整类名  */
    public const CLASSFULL = '';
    /** 元对象 命名空间 不含 meta\ 前缀  */
    public const NAMESPACE = '';
    /** 元对象 类名 不含命名空间 */
    public const CLASSNAME = '';
    /** 数据库配置键 来自 config/database.php */
    public const DBKEY = '';
    /** 对应的数据库名 */
    public const DBNAME = '';
    /** 对应的表名 */
    public const TABLENAME = '';
    /** 完整的数据库+表名 */
    public const TABLE = '';
    /** 主键字段名 */
    public const PRIMARY_FIELD_NAME = '';
    /** 主键属性名 */
    public const PRIMARY = '';
    /** 自增属性名称 */
    public const AUTO_ATTR = '';
    /** 类属性与表字段映射 */
    public const ATTR_LNK_FIELD = [];
    /** 表字段与类属性映射 */
    public const FIELD_LNK_ATTR = [];
    /** 属性完整定义信息 */
    public const ATTRS = [];
    /** 虚拟外键约束, 非数据库约束, 用程序来实现约束 */
    public const VIRTUALLY_FOREIGNS = [];
    /** 虚拟外键约束, 更新时需要处理的属性索引 */
    public const VIRTUALLY_FOREIGNS_ONUPDATE_ATTR_INDEXS = [];
    /** 扩展类型定义 */
    public const EXT_TYPE_INFO = null;

    /** 是否是新建的空对象 */
    protected bool $baseIsNewCreate;
    /** 有过修改的值, 修改前的值 */
    protected array $baseValuesOnSetBefore = [];
    /** 默认查询 Sqler */
    protected Sqler $baseSqler;
    /** 事件管理类 */
    protected static EventManager $eventManager;

    /**
     * 构造
     * @param array $attrs 属性值数组
     * @param bool $isNewCreate 是否是新创建的记录, 如果 $attrs 空, 则 $isNewCreate 默认 true
     */
    public function __construct(array $attrs = [], bool $isNewCreate = false)
    {
        if (!isset(static::$metaDb)) {
            self::getDB();
        }
        if ($attrs) {
            $this->baseIsNewCreate =  $isNewCreate;
            $this->fillData($attrs);
        } else {
            $this->baseIsNewCreate = true;
        }
    }

    public static function error(string $message)
    {
        error(__CLASS__ . ' ERROR: ' . $message, null, [
            [__FILE__, __LINE__],
            DIR_MVC . '/BaseMeta.php',
            DIR_MVC . '/DB.php'
        ]);
    }

    public function __get(string $attrName): mixed
    {
        if (DEBUG && !isset(static::ATTR_LNK_FIELD[$attrName])) {
            self::error(' 未定义的属性名称 ' . $attrName);
        }
        return $this->$attrName;
    }

    public function __set(string $attrName, mixed $value)
    {
        if (DEBUG && !isset(static::ATTR_LNK_FIELD[$attrName])) {
            static::error(' 未定义的属性名称 ' . $attrName);
        }
        if (!$this->baseIsNewCreate && $value !== $this->$attrName && !array_key_exists($attrName, $this->baseValuesOnSetBefore)) {
            // 不是新记录，则记录原始值
            $this->baseValuesOnSetBefore[$attrName] = $this->$attrName;
        }
        $this->$attrName = $value;
    }

    /**
     * 查单条记录
     *
     * @param array $where
     * @return self
     */
    public static function baseGet(array $where): ?self
    {
        $q = self::getDB()->newSqler();
        $q->table(static::TABLE)->where($where);
        $row = $q->row();
        return $row ? self::fromDBRow($row) : null;
    }

    /** 注入*/
    public static function inject($meta)
    {
        ;
    }

    /**
     * 获取数据库连接对象
     *
     * @return DB
     */
    public static function getDB(): DB
    {
        if (!isset(static::$metaDb)) {
            static::$metaDb = Container::get('db.' . static::DBKEY);
        }
        return static::$metaDb;
    }

    /**
     * 获取默认查询对象
     *
     * @return Sqler
     */
    public function getSqler(): Sqler
    {
        if (!isset($this->baseSqler)) {
            $this->baseSqler = self::getDB()->newSqler();
        }
        return $this->baseSqler;
    }

    /**
     * 获取最后查询 SQL
     *
     * @return string
     */
    public static function last(): string
    {
        return static::$metaDb->last();
    }

    /**
     * 填充数据到属性
     *
     * @param array $data 元对象属性值数组
     */
    public function fillData(array $data)
    {
        foreach (static::ATTR_LNK_FIELD as $attrName => $fieldName) {
            if (isset($data[$attrName])) {
                $this->$attrName = $data[$attrName];
            }
        }
    }

    /**
     * 填充数据到属性 通过 表记录数据
     *
     * @param array $row
     * @return array
     */
    public function fillDataByRow(array $row)
    {
        foreach (static::FIELD_LNK_ATTR as $fieldName => $attrName) {
            if (isset($row[$fieldName])) {
                $value = $row[$fieldName];
                $phpType = static::ATTRS[$attrName]['phpType'];
                if ($phpType == 'array') {
                    try {
                        $this->$attrName = $value ? json_decode($value, true) : (static::ATTRS[$attrName]['typeNull'] == '?' ? null : []);
                    } catch (\Throwable $th) {
                        self::error("解析JSON失败, 属性 {$attrName} 的 PHP 类型为 {$phpType} 类型的值, 数据库字段类型为 " . static::ATTRS[$attrName]['mysqlType'] . ', ' . $th->getMessage());
                    }
                } elseif ($phpType == 'int') {
                    if (is_numeric($value)) {
                        $this->$attrName = $value;
                    }
                } else {
                    $this->$attrName = $value;
                }
            }
        }
    }

    /**
     * 创建元对象实例, 通过数组数据
     *
     * @param array $array 元对象属性值数组
     * @return static
     */
    public static function fromArray(array $array): static
    {
        return new static($array);
    }

    /**
     * 创建元对象实例, 通过表记录数据
     *
     * @param array $data
     * @return static
     */
    public static function fromDBRow(array $row): static
    {
        $meta = new static();
        $meta->fillDataByRow($row);
        $meta->baseIsNewCreate = false;
        static::inject($meta); // 注入
        return $meta;
    }

    /**
     * 返回类属性值数组
     * @param  array $allowAttrs 指定返回的属性
     * @return array
     */
    public function toArray(array $allowAttrs = null): array
    {
        $data = [];
        foreach (static::ATTR_LNK_FIELD as $attrName => $fieldName) {
            if ($allowAttrs && !in_array($attrName, $allowAttrs)) {
                continue;
            }
            $data[$attrName] = $this->{$attrName} ?? null;
        }
        return $data;
    }

    /**
     * 元数组据列表转二维数组
     *
     * @return array
     */
    public static function listToArray(array $list): array
    {
        $data = [];
        foreach ($list as $item) {
            $data[] = $item->toArray();
        }
        return $data;
    }

    /**
     * 返回数据表记录值
     *
     * @return array
     */
    public function toDBRow(): array
    {
        $data = [];
        foreach (static::ATTR_LNK_FIELD as $attrName => $fieldName) {
            $phpType = static::ATTRS[$attrName]['phpType'];
            $mysqlDefaultValue = static::ATTRS[$attrName]['mysqlDefaultValue'];
            try {
                if ($phpType == 'array' && !is_null($this->{$attrName})) {
                    $data[$fieldName] = json_encode($this->{$attrName}, JSON_UNESCAPED_UNICODE);
                } elseif ($mysqlDefaultValue == 'CURRENT_TIMESTAMP') {
                    // 使用数据库的当前时间函数;
                } elseif (static::ATTRS[$attrName]['isVirtually']) {
                    // 虚拟字段
                } else {
                    $data[$fieldName] = $this->{$attrName};
                }
            } catch (\Throwable $th) {
                $caption = static::ATTRS[$attrName]['caption'];
                self::error("属性 {$attrName} ({$caption}) 没有默认值, 必须指定一个 {$phpType} 类型的值, " . $th->getMessage());
            }
        }
        return $data;
    }

    /**
     * 获取全部元数据对象
     *
     * @return array
     */
    public static function all(): array
    {
        $q = self::getDB()->newSqler();
        $rows = $q->table(static::TABLE)->all();
        $data = [];
        if ($rows) {
            foreach ($rows as $row) {
                $data[] = self::fromDBRow($row);
            }
        }
        return $data;
    }

    /**
     * 属性名规则条件 转 where
     *
     * @param array $conditions 属性名规则条件
     * @param array $joinMetas 联表所使用到的元对象 ['as Name' => ['class' => 'Meta Class Name']]
     * - 主表 ['SELECT' => ['class' => 'Select Meta Class Name', 'as Name']]
     */
    public static function conditionsToWhere(array $conditions, array $joinMetas = []): array
    {
        if (isset($conditions['JOIN'])) {
            $conditions['JOIN'] = self::parseJoin($joinMetas, $conditions['JOIN']);
        }
        if (isset($conditions['LEFT JOIN'])) {
            $conditions['LEFT JOIN'] = self::parseJoin($joinMetas, $conditions['LEFT JOIN']);
        }
        if (isset($conditions['FULL JOIN'])) {
            $conditions['FULL JOIN'] = self::parseJoin($joinMetas, $conditions['FULL JOIN']);
        }
        return self::processConditionsToWhere($conditions, $joinMetas);
    }

    /**
     * 过程 - 属性名规则条件 转 where
     *
     * @param array $conditions 属性名规则条件
     */
    public static function processConditionsToWhere(array $conditions, array &$joinMetas, string $parentKey = ''): array
    {
        $result = [];
        foreach ($conditions as $attrNameRule => $value) {
            $newKey = $attrNameRule;
            $newValue = $value;
            $parts = self::parseAsAttrName($attrNameRule, $joinMetas);
            if ($parts) {
                $newKey = $parts['as'] . '.' . $parts['fieldName'] . $parts['rule'] . $parts['mark'];
            }
            if (is_array($value)) {
                $result[$newKey] = self::processConditionsToWhere($value, $joinMetas, $newKey);
            } else {
                if (is_string($value)) {
                    if (substr($value, 0, 1) == '@') {
                        $newValue = self::attrNameToFieldNameInValue($value, $joinMetas);
                    } elseif ($parentKey == 'FIELDS') {
                        $parts = self::parseAsAttrNameForField($value, $joinMetas);
                        if (is_array($parts)) {
                            $newValue = $parts['fieldName'];
                        } else {
                            $newValue = $parts;
                        }
                    } elseif ($parentKey == 'GROUP BY') {
                        $parts = self::parseAsAttrName($value, $joinMetas);
                        if ($parts) {
                            $newValue = $parts['as'] . '.' . $parts['fieldName'] . $parts['rule'] . $parts['mark'];
                        }
                    } elseif ($parentKey == 'ORDER BY') {
                        $parts = self::parseAsAttrName($value, $joinMetas);
                        if ($parts) {
                            $newValue = $parts['as'] . '.' . $parts['fieldName'] . $parts['rule'] . $parts['mark'];
                        }
                    }
                }
                $result[$newKey] = $newValue;
            }
        }
        return $result;
    }

    /**
     * 分析规则名称, 属性名转字段名
     *
     * @param string $attrNameRule
     * @return array
     */
    public static function parseAsAttrName(string $attrNameRule, array &$joinMetas): array
    {
        $result = [];
        $pattern = '/^([a-zA-Z0-9\_]+)(\.(?P<attr>[a-zA-Z0-9\_]+))?(\s*\{.*\})?(\s*\#.*)?$/';
        if (preg_match($pattern, $attrNameRule, $m)) {
            $m['attr'] = $m['attr'] ?? '';
            $attr = $m['attr'] ?: $m[1];
            if ($m['attr']) {
                $as = $m[1];
                if (isset($joinMetas[$as])) {
                    $class = $joinMetas[$as]['class'];
                    if (isset($class::ATTR_LNK_FIELD[$attr])) {
                        $result['fieldName'] = $class::ATTR_LNK_FIELD[$attr];
                    } else {
                        $result['fieldName'] = $attr;
                        //self::error($class . ' 未定义的属性名称 ' . $attr);
                    }
                }
            } else {
                if (isset($joinMetas['SELECT'])) {
                    $as = $joinMetas['SELECT']['as'];
                } else {
                    $as = static::CLASSNAME;
                }
                if (isset(static::ATTR_LNK_FIELD[$attr])) {
                    $result['fieldName'] = static::ATTR_LNK_FIELD[$attr];
                }
            }
            if ($result) {
                $result['as'] = $as;
                $result['rule'] = $m[4] ?? '';
                $result['mark'] = $m[5] ?? '';
            }
        }
        return $result;
    }
    /**
     * 分析规则名称, fields 中的 属性名转字段名
     *
     * @param string $attrNameRule
     * @return array
     */
    public static function parseAsAttrNameForField(string|array $attrName, array &$joinMetas): string|array
    {
        if (is_array($attrName)) {
            if (isset($attrName['fieldName'])) {
                return $attrName['fieldName']; // 已经转换过了
            }
            $fieldRule = [$attrName[0], $attrName[1]];
            $attrName = $attrName['attr'];
            list($attrName, $fieldMark) = explode('#', $attrName . '#');
        }
        if (strpos($attrName, '*') !== false) {
            return $attrName;
        }
        $pattern = '/^(?P<table>[a-zA-Z0-9\_]+)(\.(?P<attr>[a-zA-Z0-9\_]+))?(\((?P<as>[a-zA-Z0-9\_\.\`]+)\))?$/';
        if (preg_match($pattern, $attrName, $m)) {
            if (!isset($m['attr'])) {
                // 没有指定表的情况
                $table = '';
                $attr = $m['table'];
            } else {
                $table = $m['table'];
                $attr = $m['attr'];
            }
            $as = $m['as'] ?? '';
            if ($table && isset($joinMetas[$table])) {
                $class = $joinMetas[$table]['class'];
            } elseif (isset($joinMetas['SELECT'])) {
                $class = $joinMetas['SELECT']['class'];
            } else {
                $class = static::CLASSNAME;
            }
            if (isset($class::ATTR_LNK_FIELD[$attr])) {
                if ($table) {
                    $table .= '.';
                }
                if ($as) {
                    $as = ' AS `' . $as . '`';
                }
                $fieldName = sprintf("%s%s%s", $table, $class::ATTR_LNK_FIELD[$attr], $as);
            } else {
                $fieldName =  $attr;
                //self::error($class . ' 未定义的属性名称 ' . $attr);
            }
        } else {
            $fieldName =  $attrName;
        }
        if (isset($fieldRule)) {
            if ($fieldMark) {
                $fieldName .= '#' . $fieldMark;
            }
            return [
                'fieldName' => $fieldName,
                'fieldRule' => $fieldRule
            ];
        } else {
            return $fieldName;
        }
    }

    /**
     * 分析规则名称, 字段名转属性名
     *
     * @param string $attrNameRule
     */
    public static function parseAsFieldName(string $fieldName, array &$joinMetas): string
    {
        $result = $fieldName;
        if (isset(static::FIELD_LNK_ATTR[$fieldName])) {
            $result = static::FIELD_LNK_ATTR[$fieldName];
        } else {
            $pattern = '/^(?P<as>[a-zA-Z0-9\_]+)\.(?P<field>[a-zA-Z0-9\_]+)(\s*\{.*\})?(\s*\#.*)?$/';
            if (preg_match($pattern, $fieldName, $m)) {
                $as = $m['as'];
                $field = $m['field'];
                if (isset($joinMetas[$as])) {
                    $class = $joinMetas[$as]['class'];
                    if (isset($class::FIELD_LNK_ATTR[$field])) {
                        $result = $class::FIELD_LNK_ATTR[$field];
                    } else {
                        self::error($class . ' 未定义的字段名称 ' . $fieldName);
                    }
                }
            }
        }
        return $result;
    }

    /**
     * 以 @ 开头的原生 SQL 中 属性名 转字段名
     *
     * @param string $originalSql 例 @id 或 MID(caption, 1, 5)
     */
    public static function attrNameToFieldNameInValue(string $originalSql, array &$joinMetas): string
    {
        $pattern = '/\b([a-zA-Z\_][a-zA-Z0-9\_]+)\b(\.\b(?P<attr>[a-zA-Z\_][a-zA-Z0-9\_]+)\b)?/';
        return preg_replace_callback($pattern, function ($m) use ($joinMetas) {
            $m['attr'] = $m['attr'] ?? '';
            $attr = $m['attr'] ?: $m[1];
            if ($m['attr']) {
                $as = $m[1];
                if (isset($joinMetas[$as])) {
                    $class = $joinMetas[$as]['class'];
                    if (isset($class::ATTR_LNK_FIELD[$attr])) {
                        return trim($as, '`') . '.' . $class::ATTR_LNK_FIELD[$attr];
                    } else {
                        self::error($class . ' 未定义的属性名称 ' . $attr);
                    }
                }
            } else {
                $as = static::CLASSNAME;
                if (isset(static::ATTR_LNK_FIELD[$attr])) {
                    return $as . '.' . static::ATTR_LNK_FIELD[$attr] ;
                }
            }
            return $m[0];
        }, $originalSql);
    }

    /**
     * 分析 JOIN 语句, 将所有用到的元对象类找出来, 并将命名空间替换成实际数据库名
     * - 以 @ 开头为 原始库名, 原始表名, 例 ['@yiparts.xmvc_user']
     */
    public static function parseJoin(array &$metas, array $joins): array
    {
        $result = [];
        foreach ($joins as $key => $value) {
            $newKey = $key;
            if (substr($key, 0, 1) == '@') {
                // 以 @ 开头为 原始库名, 原始表名
                $newKey = substr($key, 1);
            } else {
                $pattern = '/^([a-zA-Z0-9\_]+)(\.(?P<class>[a-zA-Z0-9\_]+))?(\((?P<as>[a-zA-Z0-9\_]+)\))?$/';
                if (preg_match($pattern, $key, $m)) {
                    $m['class'] = $m['class'] ?? '';
                    $classname = $m['class'] ?: $m[1];
                    $as = $m['as'] ?? $classname;
                    if ($m['class']) {
                        $namespace = $m[1];
                    } else {
                        $namespace = static::NAMESPACE;
                    }
                    $classFilename = self::getClassFilename($namespace, $classname);
                    if (is_file($classFilename)) {
                        $class = self::getClassFullName($namespace, $classname);
                        if (class_exists($class, true)) {
                            $metas[$as] = [
                                'class' => $class,
                                'as' => $as,
                            ];
                            // 命名空间替换成实际数据库名
                            if (strpos($namespace, '_') === false) {
                                $newKey = $class::TABLE . '(' . $as . ')';
                            }
                        }
                    }
                }
            }
            $result[$newKey] = $value;
        }
        return $result;
    }

    /** 获取类的完整名称 含有命名空间 */
    public static function getClassFullName(string $namespace, string $classname)
    {
        return '\\app\\meta\\' . $namespace . '\\' . $classname;
    }

    /** 获取类的php文件名 */
    public static function getClassFilename(string $namespace, string $classname)
    {
        return ROOT . '/meta/' . $namespace . '/' . $classname . '.php';
    }

    /**
     * 通过过滤条件获取元数据对象
     *
     * @param array $conditions 过滤条件, 类似 Sqler::where() 但使用 属性名 代替字段名, 为 null 取全部
     * @param mixed $value 单属性条件 filter('id', 12)
     * @return array
     */
    public static function filter(string|array $conditions = null, mixed $value = null): array
    {
        $q = self::getDB()->newSqler();
        if (empty($conditions)) {
            $rows = $q->table(static::TABLE, static::CLASSNAME)->all();
        } else {
            if (is_string($conditions)) {
                $conditions = [$conditions => $value];
            }
            $conditions = self::conditionsToWhere($conditions);
            $rows = $q->table(static::TABLE, static::CLASSNAME)->where($conditions)->all();
        }
        $data = [];
        if ($rows) {
            foreach ($rows as $row) {
                $data[] = self::fromDBRow($row);
            }
        }
        return $data;
    }

    /**
     * 通过过滤条件获取元数据对象 - 只取一条
     *
     * @param array $conditions 过滤条件, 类似 Sqler::where() 但使用 属性名 代替字段名, 为 null 取全部
     * @param mixed $value 单属性条件 filter('id', 12)
     * @return static
     */
    public static function filterFirst(string|array $conditions = null, mixed $value = null): ?static
    {
        $q = self::getDB()->newSqler();
        if (empty($conditions)) {
            $row = $q->table(static::TABLE, static::CLASSNAME)->limit(0, 1)->row();
        } else {
            if (is_string($conditions)) {
                $conditions = [$conditions => $value];
            }
            $conditions = self::conditionsToWhere($conditions);
            $row = $q->table(static::TABLE, static::CLASSNAME)->where($conditions)->limit(0, 1)->row();
        }
        $meta = null;
        if ($row) {
            $meta = self::fromDBRow($row);
        }
        return $meta;
    }

    /**
     * 是否存在指定条件下的数据对象
     *
     * @param array $conditions 过滤条件, 类似 Sqler::where() 但使用 属性名 代替字段名
     * @return bool
     */
    public static function has(string|array $conditions, mixed $value = null): bool
    {
        if (is_string($conditions)) {
            $conditions = [$conditions => $value];
        }
        $conditions = self::conditionsToWhere($conditions);
        $q = self::getDB()->newSqler();
        return $q->table(static::TABLE, static::CLASSNAME)->where($conditions)->has();
    }

    /** 构建主键条件 */
    protected function primaryWhere(): array
    {
        return [static::PRIMARY_FIELD_NAME => $this->{static::PRIMARY}];
    }

    /**
     * 处理虚拟外键约束
     * - CASCADE:级联删除或是级联更新, SET NULL:设为 NULL, NO ACTION:什么也不做, RESTRICT:限制约束, SET DEFAULT:设为默认值
     * @param string $onType 事件类型 onDelete 和 onUpdate
     * @param array $foreign 约束规则
     * @param mixed $referValue 外键值(也就是当前对象对应的属性值)
     */
    protected function processVirtuallyForeign(string $onType, array $foreign, mixed $referValue)
    {
        $onTypeCaption = ($onType == 'onDelete') ? '删除' : '修改';
        switch ($foreign[$onType]) {
            case 'CASCADE':
                $binderMetas = $foreign['binder']['class']::filter($foreign['binder']['attrName'], $referValue);
                foreach ($binderMetas as $binderMeta) {
                    $binderMeta->delete();
                }
                break;
            case 'RESTRICT':
                $found = $foreign['binder']['class']::has($foreign['binder']['attrName'], $referValue);
                if ($found) {
                    self::error(sprintf(
                        "虚拟外键约束[RESTRICT]: %s->%s 的值 %s 在 %s->%s 还存在, 禁止{$onTypeCaption}",
                        $foreign['refer']['class'],
                        $foreign['refer']['attrName'],
                        $referValue,
                        $foreign['binder']['class'],
                        $foreign['binder']['attrName']
                    ));
                }
                break;
            case 'SET DEFAULT':
                $nullMeta = new $foreign['binder']['class']();
                $binderMetas = $foreign['binder']['class']::filter($foreign['binder']['attrName'], $referValue);
                foreach ($binderMetas as $binderMeta) {
                    $binderMeta->{$foreign['binder']['attrName']} = $nullMeta->{$foreign['binder']['attrName']};
                    $binderMeta->save();
                }
                break;
            case 'SET NULL':
                $binderMetas = $foreign['binder']['class']::filter($foreign['binder']['attrName'], $referValue);
                foreach ($binderMetas as $binderMeta) {
                    $binderMeta->{$foreign['binder']['attrName']} = null;
                    $binderMeta->save();
                }
                break;
            case 'NO ACTION':
                # 什么也不做
                break;
            default:
                self::error('未知的外键约束类型, ' . json_encode($foreign, JSON_UNESCAPED_UNICODE));
                break;
        };
    }

    public function save(): bool
    {
        if (!$this->baseIsNewCreate && empty($this->baseValuesOnSetBefore)) {
            return true; // 没有修改过任何值
        }
        $result = false;
        try {
            static::$metaDb->transaction(function (Sqler $q, DB $db) use (&$result) {
                $q->table(static::TABLE);
                if ($this->baseIsNewCreate) {
                    $row = $this->toDBRow();
                    $q->set($row);
                    $db->insert($q);
                    if (static::AUTO_ATTR != '') {
                        $this->{static::AUTO_ATTR} = $db->insertId();
                    }
                    trigger(MetaInsertEvent::class, new MetaInsertEvent($this));
                } else {
                    foreach ($this->baseValuesOnSetBefore as $attrName => $oldValue) {
                        // 处理的更新时需要虚拟外键约束
                        if (!is_null(static::VIRTUALLY_FOREIGNS_ONUPDATE_ATTR_INDEXS) && isset(static::VIRTUALLY_FOREIGNS_ONUPDATE_ATTR_INDEXS[$attrName])) {
                            foreach (static::VIRTUALLY_FOREIGNS_ONUPDATE_ATTR_INDEXS[$attrName] as $index) {
                                $foreign = static::VIRTUALLY_FOREIGNS[$index];
                                $this->processVirtuallyForeign('onUpdate', $foreign, $oldValue);
                            }
                        }
                        $value = $this->$attrName;
                        if (is_array($value)) {
                            $value = json_encode($value, JSON_UNESCAPED_UNICODE);
                        }
                        $q->set(static::ATTR_LNK_FIELD[$attrName], $value);
                    }
                    // 更新当前记录
                    $q->where($this->primaryWhere());
                    $db->update($q);

                    trigger(MetaUpdateEvent::class, new MetaUpdateEvent($this, $this->baseValuesOnSetBefore));

                    // 清空值变化记录
                    $this->baseValuesOnSetBefore = [];
                }
                $result = true;
            });
        } catch (\Throwable $th) {
            throw new Exception($th->getMessage(), 1);
        }
        return $result;
    }

    /**
     * 删除记录
     *
     */
    public function delete()
    {
        if ($this->baseIsNewCreate) {
            return;
        }
        static::$metaDb->transaction(function (Sqler $q, \xmvc\DB $db) {
            $q->table(static::TABLE)->where($this->primaryWhere());
            // 处理虚拟外键约束
            if (!is_null(static::VIRTUALLY_FOREIGNS)) {
                foreach (static::VIRTUALLY_FOREIGNS as $foreign) {
                    $attrName = $foreign['refer']['attrName'];
                    $this->processVirtuallyForeign('onDelete', $foreign, $this->{$attrName});
                }
            }
            // 删除当前记录
            $db->delete($q);

            trigger(MetaDeleteEvent::class, new MetaDeleteEvent($this));
        });
    }

    /** 获取 YAML 配置数据 */
    public static function getYamlData()
    {
        $dir = ROOT . '/metaConf/yaml/' . static::NAMESPACE;
        $filename = $dir . '/' . static::CLASSNAME . '.yml';
        return Yaml::parseFile($filename);
    }

    /*********************************************** */
    public function validate()
    {
        throw new Exception("Subclasses should implement this method.");
    }

    public function trackChanges()
    {
        throw new Exception("Subclasses should implement this method.");
    }

    public function softDelete()
    {
        throw new Exception("Subclasses should implement this method.");
    }

    public function cache()
    {
        throw new Exception("Subclasses should implement this method.");
    }
}
