_with = $data; return $this; } /** * @return array|null */ public function getWith(): array|null { return $this->_with; } /** * object init */ public function clean() { $this->_attributes = []; $this->_oldAttributes = []; } /** * @throws NotFindClassException * @throws ReflectionException * @throws Exception */ public function init() { if (!Context::hasContext(Relation::class)) { $relation = Snowflake::createObject(Relation::class); $this->_relation = Context::setContext(Relation::class, $relation); } else { $this->_relation = Context::getContext(Relation::class); } } /** * @param $name * @param $method * @return static */ public function addGets($name, $method): static { if (!isset($this->_annotations[self::GET])) { $this->_annotations[self::GET] = []; } $this->_annotations[self::GET][$name] = [$this, $method]; return $this; } /** * @param $name * @param $method * @return static */ public function addSets($name, $method): static { if (!isset($this->_annotations[self::SET])) { $this->_annotations[self::SET] = []; } $this->_annotations[self::SET][$name] = [$this, $method]; return $this; } /** * @return array */ public function getActions(): array { return $this->actions; } /** * @return bool */ public function getIsCreate(): bool { return $this->isNewExample === TRUE; } /** * @param bool $bool * @return $this */ public function setIsCreate($bool = FALSE): static { $this->isNewExample = $bool; return $this; } /** * @return mixed * @throws Exception * get last exception or other error */ public function getLastError(): mixed { return Snowflake::app()->getLogger()->getLastError('mysql'); } /** * @return bool * @throws Exception */ public function hasPrimary(): bool { if ($this->primary !== NULL) { return true; } $primary = static::getColumns()->getPrimaryKeys(); if (!empty($primary)) { return $this->primary = is_array($primary) ? current($primary) : $primary; } return false; } /** * @throws Exception */ public function isAutoIncrement(): bool { return $this->getAutoIncrement() !== null; } /** * @throws Exception */ public function getAutoIncrement(): int|string|null { return static::getColumns()->getAutoIncrement(); } /** * @return null|string * @throws Exception */ public function getPrimary(): ?string { if (!$this->hasPrimary()) { return null; } return $this->primary; } /** * @return int|null * @throws Exception */ public function getPrimaryValue(): ?int { if (!$this->hasPrimary()) { return null; } return $this->getAttribute($this->primary); } /** * @param $param * @param null $db * @return BaseActiveRecord|null * @throws NotFindClassException * @throws ReflectionException * @throws Exception */ public static function findOne($param, $db = NULL): static|null { if (is_bool($param)) { return null; } if (is_numeric($param)) { $param = static::getPrimaryCondition($param); } return static::find()->where($param)->first(); } /** * @param $param * @return array * @throws Exception */ private static function getPrimaryCondition($param): array { $primary = static::getColumns()->getPrimaryKeys(); if (empty($primary)) { throw new Exception('Primary key cannot be empty.'); } if (is_array($primary)) { $primary = current($primary); } return [$primary => $param]; } /** * @param null $field * @return ActiveRecord|null * @throws Exception * @throws Exception */ public static function max($field = null): ?ActiveRecord { $columns = static::getColumns(); if (empty($field)) { $field = $columns->getFirstPrimary(); } $columns = $columns->get_fields(); if (!isset($columns[$field])) { return null; } $first = static::find()->max($field)->first(); if (empty($first)) { return null; } return $first[$field]; } /** * @return ActiveQuery * @throws ReflectionException * @throws NotFindClassException */ public static function find(): ActiveQuery { return Snowflake::createObject(ActiveQuery::class, [static::class]); } /** * @param null $condition * @param array $attributes * * @param bool $if_condition_is_null * @return bool * @throws Exception */ public static function deleteByCondition($condition = NULL, $attributes = [], $if_condition_is_null = false): bool { if (empty($condition)) { if (!$if_condition_is_null) { return false; } return static::find()->delete(); } $model = static::find()->ifNotWhere($if_condition_is_null)->where($condition); if (!empty($attributes)) { $model->bindParams($attributes); } return $model->delete(); } /** * @return array * @throws Exception */ public function getAttributes(): array { return $this->toArray(); } /** * @return array */ public function getOldAttributes(): array { return $this->_oldAttributes; } /** * @param $name * @param $value * @return mixed */ public function setAttribute($name, $value): mixed { return $this->_attributes[$name] = $value; } /** * @param $name * @param $value * @return mixed */ public function setOldAttribute($name, $value): mixed { return $this->_oldAttributes[$name] = $value; } /** * @param array $param * @return $this * @throws Exception */ public function setAttributes(array $param): static { if (empty($param)) { return $this; } $this->_attributes = array_merge($this->_attributes, $param); return $this; } /** * @param $param * @return $this */ public function setOldAttributes($param): static { if (empty($param) || !is_array($param)) { return $this; } foreach ($param as $key => $val) { $this->setOldAttribute($key, $val); } return $this; } /** * @param $attributes * @param $param * @return $this|bool * @throws Exception */ private function insert($param, $attributes): bool|static { if (empty($param)) { return FALSE; } $dbConnection = static::getDb(); [$sql, $param] = SqlBuilder::builder(static::find())->insert($param); // $trance = $dbConnection->beginTransaction(); try { if (!($lastId = (int)$dbConnection->createCommand($sql, static::getDbName(), $param)->save(true, $this))) { throw new Exception('保存失败.'); } // $trance->commit(); $lastId = $this->setPrimary($lastId, $param); $this->refresh(); SEvent::trigger(self::AFTER_SAVE, [$attributes, $param]); } catch (\Throwable $exception) { // $trance->rollback(); $lastId = $this->addError($exception, 'mysql'); } return $lastId; } /** * @param $lastId * @param $param * @return static * @throws Exception */ private function setPrimary($lastId, $param): static { if ($this->isAutoIncrement()) { $this->setAttribute($this->getAutoIncrement(), (int)$lastId); return $this; } if (!$this->hasPrimary()) { return $this; } $primary = $this->getPrimary(); if (!isset($param[$primary]) || empty($param[$primary])) { $this->setAttribute($primary, (int)$lastId); } return $this->setAttributes($param); } /** * @param $fields * @param $condition * @param $param * @return $this|bool * @throws Exception */ private function updateInternal($fields, $condition, $param): bool|static { if (empty($param)) { return true; } $command = static::getDb(); if ($this->hasPrimary()) { $condition = [$this->getPrimary() => $this->getPrimaryValue()]; } $generate = SqlBuilder::builder(static::find()->where($condition))->update($param); if (is_bool($generate)) { return $generate; } // $trance = $command->beginTransaction(); if (!$command->createCommand($generate[0], static::getDbName(), $generate[1])->save(false, $this)) { // $trance->rollback(); return false; } // $trance->commit(); $this->refresh(); SEvent::trigger(self::AFTER_SAVE, [$fields, $param]); return true; } /** * @param null $data * @return bool|$this * @throws Exception */ public function save($data = NULL): static|bool { if (!is_null($data)) { $this->_attributes = merge($this->_attributes, $data); } if (!$this->validator($this->rules())) { return false; } if (!SEvent::trigger(self::BEFORE_SAVE, [$this], $this)) { return false; } static::getDb()->enablingTransactions(); [$change, $condition, $fields] = $this->filtration_and_separation(); if (!$this->isNewExample) { return $this->updateInternal($fields, $condition, $change); } return $this->insert($change, $fields); } /** * @param array $rule * @return bool * @throws Exception */ public function validator(array $rule): bool { if (empty($rule)) return true; $validate = $this->resolve($rule); if (!$validate->validation()) { return $this->addError($validate->getError(), 'mysql'); } else { return TRUE; } } /** * @param $rule * @return Validator * @throws Exception */ private function resolve($rule): Validator { $validate = Validator::getInstance(); $validate->setParams($this->_attributes); $validate->setModel($this); foreach ($rule as $Key => $val) { $field = array_shift($val); if (empty($val)) { continue; } $validate->make($field, $val); } return $validate; } /** * @param string $name * @return null * @throws Exception */ public function getAttribute(string $name) { if ($this->hasAnnotation($name)) { return $this->runAnnotation($name, $this->_attributes[$name]); } return $this->_attributes[$name] ?? null; } /** * @param string $name * @param mixed $value * @param string $type * @return mixed */ protected function runAnnotation(string $name, mixed $value, string $type = self::GET): mixed { return call_user_func($this->_annotations[$type][$name], $value); } /** * @return array * @throws Exception */ private function filtration_and_separation(): array { $_tmp = []; $condition = []; $columns = static::getColumns(); $format = $columns->getAllField(); foreach ($this->_attributes as $key => $val) { $oldValue = $this->_oldAttributes[$key] ?? null; if ($val !== $oldValue) { $_tmp[$key] = $this->toFormat($columns, $format, $key, $val); if (is_bool($_tmp[$key])) { unset($_tmp[$key]); } } else { $condition[$key] = $val; } } return [$_tmp, $condition, array_keys($_tmp)]; } /** * @param $columns * @param $format * @param $key * @param $value * @return mixed */ public function toFormat($columns, $format, $key, $value): mixed { if (isset($format[$key])) { return $columns->encode($value, $columns->clean($format[$key])); } return $value; } /** * @param $name * @param $value */ public function setRelate($name, $value) { $this->_relate[$name] = $value; } /** * @param $name * @return bool */ public function hasRelate($name): bool { return isset($this->_relate[$name]); } /** * @param array $relates */ public function setRelates(array $relates) { if (empty($relates)) { return; } foreach ($relates as $key => $val) { $this->setRelate($key, $val); } } /** * @return array */ public function getRelates(): array { return $this->_relate; } /** * @return Relation|null */ public function getRelation(): ?Relation { return $this->_relation; } /** * @param $name * @return array|string|null * @throws Exception */ public function getRelate($name): null|array|string { return Snowflake::getAnnotation()->getRelateMethods(static::class, $name); } /** * @param $attribute * @return bool * @throws Exception */ public function has($attribute): bool { return static::getColumns()->hasField($attribute); } /**ƒ * @return string * @throws Exception */ public static function getTable(): string { $tablePrefix = static::getDb()->tablePrefix; $table = static::tableName(); if (empty($table)) { throw new Exception('You need add static method `tableName` and return table name.'); } if (str_starts_with($table, $tablePrefix)) { return '`' . static::getDbName() . '`.' . $table; } else if (!str_starts_with($table, '{{%')) { return '`' . static::getDbName() . '`.' . $table; } $table = trim($table, '{{%}}'); if ($tablePrefix) { $table = $tablePrefix . $table; } return '`' . static::getDbName() . '`.' . $table; } /** * @param $attributes * @param $changeAttributes * @return bool * @throws Exception */ #[Event(ActiveRecord::AFTER_SAVE)] public function afterSave($attributes, $changeAttributes): bool { return true; } /** * @param $model * @return bool */ #[Event(ActiveRecord::BEFORE_SAVE)] public function beforeSave($model): bool { return true; } private static string $ab_name = ''; /** * @return Connection * @throws Exception */ public static function getDb(): Connection { return static::setDatabaseConnect('db'); } /** * @return static */ public function refresh(): static { $this->_oldAttributes = $this->_attributes; return $this; } /** * @param $name * @param $value * @throws Exception */ public function __set($name, $value) { if (method_exists($this, 'set' . ucfirst($name))) { $this->{'set' . ucfirst($name)}($value); } else { $method = $this->_get_annotation($name, self::SET); if (!empty($method)) { $value = $this->{$method}($value); } $this->_attributes[$name] = $value; } } /** * @param $name * @param string $method * @return string|null * @throws Exception */ protected function _get_annotation(string $name = null, string $method = self::GET): ?string { $annotation = Snowflake::app()->getAnnotation(); if ($method == static::SET) { return $annotation->getSetMethodName(static::class, $name); } if ($method == static::GET) { return $annotation->getGetMethodName(static::class, $name); } return $annotation->getRelateMethods(static::class, $name); } /** * @param $name * @return mixed * @throws Exception */ public function __get($name): mixed { if (Snowflake::app()->has($name)) { return Snowflake::getApp($name); } else { return $this->_gets($name); } } /** * @param $name * @return mixed * @throws Exception */ private function _gets($name): mixed { $value = $this->_attributes[$name] ?? null; $method = $this->_get_annotation($name, static::GET); if (!empty($method)) { return $this->{$method}($value); } return $this->runRelation($name, $value); } /** * @param $name * @param $value * @return mixed * @throws Exception */ private function runRelation($name, $value): mixed { $relation = $this->_get_annotation($name, static::RELATE); if (empty($relation)) { return $this->_decode($name, $value); } if (($value = $this->{$relation}(...[$value])) instanceof HasBase) { return $value->get(); } return $value; } /** * @param $name * @param $value * @return mixed * @throws Exception */ private function _decode($name, $value): mixed { return static::getColumns()->_decode($name, $value); } /** * @param $name * @return mixed */ private function with($name): mixed { $data = $this->{$this->_relate[$name]}(); if ($data instanceof HasBase) { return $data->get(); } return $data; } /** * @param string $type * @return array */ protected function getAnnotation($type = self::GET): array { return $this->_annotations[$type] ?? []; } /** * @param $name * @param string $type * @return bool */ protected function hasAnnotation($name, $type = self::GET): bool { if (!isset($this->_annotations[$type])) { return false; } return isset($this->_annotations[$type][$name]); } /** * @param $item * @param $data * @return array */ protected function resolveAttributes($item, $data): array { return call_user_func($item, $data); } /** * @param $name * @return bool */ public function __isset($name): bool { return isset($this->_attributes[$name]); } /** * @param $call * @return mixed * @throws Exception */ private function resolveClass($call): mixed { if ($call instanceof HasOne) { return $call->get(); } else if ($call instanceof HasMany) { return $call->get(); } else { return $call; } } /** * @param mixed $offset * @return bool * @throws Exception */ public function offsetExists(mixed $offset): bool { return isset($this->_attributes[$offset]) || isset($this->_oldAttributes[$offset]); } /** * @param mixed $offset * @return mixed * @throws Exception */ public function offsetGet(mixed $offset): mixed { return $this->__get($offset); } /** * @param mixed $offset * @param mixed $value * @throws Exception */ public function offsetSet(mixed $offset, mixed $value) { $this->__set($offset, $value); } /** * @param mixed $offset * @throws Exception */ public function offsetUnset(mixed $offset) { if (!isset($this->_attributes[$offset]) && !isset($this->_oldAttributes[$offset])) { return; } unset($this->_attributes[$offset]); unset($this->_oldAttributes[$offset]); if (isset($this->_relate)) { unset($this->_relate[$offset]); } } /** * @return array */ public function unset(): array { $fields = func_get_args(); $fields = array_shift($fields); if (!is_array($fields)) { $fields = explode(',', $fields); } $array = array_combine($fields, $fields); return array_diff_assoc($array, $this->_attributes); } /** * @param $dbName * @return mixed * @throws Exception */ public static function setDatabaseConnect($dbName): Connection { static::$ab_name = $dbName; return Snowflake::app()->db->get($dbName); } /** * @return string * @throws ConfigException */ public static function getDbName(): string { return Config::get('databases.connections.' . static::$ab_name . '.database'); } /** * @return Columns * @throws Exception */ public static function getColumns(): Columns { return static::getDb()->getSchema() ->getColumns() ->table(static::getTable()); } /** * @param array $data * @return static * @throws */ public static function populate(array $data): static { // $class = Snowflake::app()->getChannel(); // /** @var static $model */ // $model = $class->pop(static::class, function () { // return new static(); // }); $model = new static(); $model->_attributes = $data; $model->_oldAttributes = $data; $model->setIsCreate(false); return $model; } }