init(); } /** * @return array */ public function rules(): array { return []; } /** * @param array $data * * @return Model */ public function setWith(array $data): static { $this->_with = $data; return $this; } /** * @return array */ public function getWith(): array { return $this->_with; } /** * @return bool */ public function hasWith(): bool { return count($this->_with) > 0; } /** * object init */ public function clean(): void { $this->_attributes = []; $this->_oldAttributes = []; } /** * @throws Exception */ public function init(): void { $container = Kiri::getDi(); $container->resolveProperties($container->getReflectionClass(get_called_class()), $this); } /** * @return bool */ public function getIsNowExample(): bool { return $this->isNewExample === TRUE; } /** * @param bool $bool * * @return $this */ public function setIsNowExample(bool $bool = FALSE): static { $this->isNewExample = $bool; return $this; } /** * @return string * @throws * get last exception or other error */ public function getLastError(): string { return $this->getLogger()->getLastError('mysql'); } /** * @return bool * @throws */ public function hasPrimary(): bool { return $this->primary != ''; } /** * @return null|string * @throws */ public function getPrimary(): string { if (!$this->hasPrimary()) { return ''; } return $this->primary; } /** * @return bool * @throws */ public function hasPrimaryValue(): bool { if ($this->hasPrimary()) { return $this->getPrimaryValue() === NULL; } return FALSE; } /** * @return int|null * @throws */ public function getPrimaryValue(): ?int { if ($this->hasPrimary() && isset($this->_oldAttributes[$this->getPrimary()])) { return (int)$this->_oldAttributes[$this->getPrimary()]; } else { return NULL; } } /** * @param int|string|array $param * * @return Model|null * @throws */ public static function findOne(int|string|array|null $param): ?static { if (empty($param)) { return NULL; } $query = new ActiveQuery($model = static::instance()); $query->from($model->getTable())->alias('t1'); if (is_numeric($param)) { $query->where([$model->getPrimary() => +$param]); } else if (is_array($param)) { $query->where($param); } else { $query->whereRaw($param); } if (($data = $query->first()) === FALSE) { throw new Exception($model->getLastError()); } else { return $data; } } /** * @param int $param * * @return Model|null * @throws */ public static function primary(int $param): ?static { $model = static::instance(); $query = new ActiveQuery($model); $query->from($model->getTable())->alias('t1')->where([$model->getPrimary() => $param]); return $query->first(); } /** * @return bool|int * @throws Exception */ public function optimize(): bool|int { return static::query()->execute('OPTIMIZE TABLE ' . $this->getTable()); } /** * @return static */ protected static function instance(): static { return new static(); } /** * @param int|string|array $condition * * @return static|null * @throws */ public static function first(int|string|array $condition): ?static { return static::findOne($condition); } /** * @param string|array $condition * * @return Collection * @throws */ public static function all(string|array $condition): Collection { $model = new ActiveQuery(static::instance()); $model->from($model->getTable())->alias('t1'); if (is_array($condition)) { $model->where($condition); } else { $model->whereRaw($condition); } return $model->get(); } /** * @return ActiveQuery * @throws */ public static function query(): ActiveQuery { $model = new ActiveQuery(static::instance()); $model->select()->from($model->getTable())->alias('t1'); return $model; } /** * @return Connection * @throws */ public function getConnection(): Connection { return Kiri::getDi()->get(DatabasesProviders::class)->get($this->connection); } /** * @param array|string $condition * @param array $attributes * * @return bool */ protected static function deleteByCondition(array|string $condition = [], array $attributes = []): bool { $model = static::query(); $model->bindParams($attributes); if (is_string($condition)) { $model->whereRaw($condition); } else { $model->where($condition); } return $model->delete(); } /** * @return array * @throws */ public function getAttributes(): array { return $this->_attributes; } /** * @return array */ public function getOldAttributes(): array { return $this->_oldAttributes; } /** * @param string $name * @param mixed $value * * @return mixed */ public function setAttribute(string $name, mixed $value): mixed { $method = 'set' . ucfirst($name) . 'Attribute'; if (method_exists($this, $method)) { $value = $this->{$method}($value); } return $this->_attributes[$name] = $value; } /** * @param string $name * @param mixed $value * * @return mixed */ public function setOldAttribute(string $name, mixed $value): mixed { $method = 'set' . ucfirst($name) . 'Attribute'; if (method_exists($this, $method)) { $value = $this->{$method}($value); } return $this->_oldAttributes[$name] = $value; } /** * @param array $param * * @return $this * @throws */ public function setAttributes(array $param): static { foreach ($param as $key => $attribute) { $this->setAttribute($key, $attribute); } return $this; } /** * @param array $param * * @return $this */ public function setOldAttributes(array $param): static { foreach ($param as $key => $attribute) { $this->setOldAttribute($key, $attribute); } return $this; } /** * @return $this|bool * @throws */ private function insert(): bool|static { $sql = SqlBuilder::builder($query = static::query())->insert($this->_attributes); $lastId = $this->getConnection()->createCommand($sql, $query->params)->exec(); if ($lastId === FALSE) { return FALSE; } if (!$this->hasPrimary()) { return $this->refresh()->afterSave($this->_attributes, []); } $this->_attributes[$this->getPrimary()] = $lastId; return $this->refresh()->afterSave($this->_attributes, [$this->getPrimary() => $lastId]); } /** * @param array $old * @param array $condition * @param array $change * * @return $this|bool * @throws */ protected function updateInternal(array $old, array $condition, array $change): bool|static { $query = static::query()->where($condition); if (count($change) < 1) { return TRUE; } $generate = SqlBuilder::builder($query)->update($change); if ($generate === FALSE) { return FALSE; } if (!$this->getConnection()->createCommand($generate, $query->params)->exec()) { return FALSE; } return $this->refresh()->afterSave($old, $change); } /** * @return bool|$this * @throws */ public function save(): static|bool { if (!$this->validator($this->rules(), $this->_attributes) || !$this->beforeSave($this)) { return FALSE; } if (!$this->isNewExample) { return $this->updateInternal(...$this->arrayIntersect($this->_attributes)); } else { return $this->insert(); } } /** * @return array * @throws */ protected function arrayIntersect(array $params): array { $condition = []; $oldPrams = []; foreach ($this->_oldAttributes as $key => $attribute) { if (!array_key_exists($key, $params) || $params[$key] == $attribute) { $condition[$key] = $attribute; unset($params[$key]); } else { $oldPrams[$key] = $this->_oldAttributes[$key]; } } if ($this->hasPrimary()) { $condition = [$this->getPrimary() => $this->getPrimaryValue()]; } return [$oldPrams, $condition, $params]; } /** * @return array */ public function getChanges(): array { if (!$this->isNewExample) { return \array_intersect_assoc($this->_oldAttributes, $this->_attributes); } return $this->_attributes; } /** * @param array $value * * @return $this */ public function populates(array $value): static { $this->_attributes = $value; $this->_oldAttributes = $value; $this->setIsNowExample(); return $this; } /** * @param array $rule * * @return bool * @throws */ public function validator(array $rule, array $params): bool { if (count($rule) < 1 || $this->skipValidate) { return TRUE; } $validate = $this->resolve($rule); if (!$validate->validation($params)) { return \Kiri::getLogger()->failure($validate->getError() . PHP_EOL, 'mysql'); } else { return TRUE; } } /** * @param array $rule * * @return Validator * @throws */ private function resolve(array $rule): Validator { $validate = new Validator(); foreach ($rule as $val) { $field = array_shift($val); if (is_string($field)) { $validate->make($this, [$field], $val); } else { $validate->make($this, $field, $val); } } return $validate; } /** * @param string $name * * @return null * @throws */ public function getAttribute(string $name): mixed { return $this->_attributes[$name] ?? NULL; } /** * @return Relation|null * @throws */ public function getRelation(): ?Relation { return di(Relation::class); } /** * @param string $attribute * * @return bool * @throws */ public function has(string $attribute): bool { return TRUE; } /**ƒ * @return string * @throws */ public function getTable(): string { $connection = $this->getConnection(); $tablePrefix = $connection->tablePrefix; if (empty($this->table)) { throw new Exception('You need add static method `tableName` and return table name.'); } $table = trim($this->table, '{%}'); if (!empty($tablePrefix) && !str_starts_with($table, $tablePrefix)) { $table = $tablePrefix . $table; } return '`' . $connection->database . '`.' . $table; } /** * @param array $oldAttributes * @param array $changeAttributes * * @return bool */ public function afterSave(array $oldAttributes, array $changeAttributes): bool { return TRUE; } /** * @param self $model * * @return bool * @throws */ public function beforeSave(self $model): bool { return TRUE; } /** * @return static */ public function refresh(): static { $this->_oldAttributes = $this->_attributes; $this->isNewExample = FALSE; return $this; } /** * @param string $name * @param mixed $value * * @return void */ public function __set(string $name, mixed $value): void { $prefix = 'set' . ucfirst($name); if (method_exists($this, $prefix)) { $this->{$prefix}($value); return; } $method = $prefix . 'Attribute'; if (method_exists($this, $method)) { $this->_attributes[$name] = $this->{$method}($value); } else { $this->_attributes[$name] = $value; } } /** * @param string $name * * @return mixed */ public function __get(string $name): mixed { $value = $this->_attributes[$name] ?? NULL; if (!method_exists($this, 'get' . ucfirst($name))) { return $this->withPropertyOverride($name, $value); } else { return $this->withRelate($name); } } /** * @param string $name * @param mixed|null $value * * @return mixed */ protected function withPropertyOverride(string $name, mixed $value = NULL): mixed { $method = 'get' . ucfirst($name) . 'Attribute'; if (method_exists($this, $method)) { return $this->{$method}($value); } else { return $value; } } /** * @param string $name * @param string $prefix * * @return bool */ protected function hasRelateMethod(string $name): bool { $reflection = $this->container->get(static::class); if (!$reflection->hasMethod($name)) { return FALSE; } if ($reflection->getMethod($name)->isStatic() || !$reflection->getMethod($name)->isPublic()) { return FALSE; } else { return TRUE; } } /** * @param string $name * * @return mixed */ protected function withRelate(string $name): mixed { $response = $this->{'get' . ucfirst($name)}(); if ($response instanceof \Database\Traits\Relation) { $response = $response->get(); } return $response; } /** * @param $name * * @return bool */ public function __isset(string $name): bool { return isset($this->_attributes[$name]); } /** * @param mixed $offset * * @return bool * @throws */ public function offsetExists(mixed $offset): bool { return isset($this->_attributes[$offset]) || isset($this->_oldAttributes[$offset]); } /** * @param mixed $offset * * @return mixed * @throws */ public function offsetGet(mixed $offset): mixed { return $this->__get($offset); } /** * @param mixed $offset * @param mixed $value * * @throws */ #[ReturnTypeWillChange] public function offsetSet(mixed $offset, mixed $value): void { $this->__set($offset, $value); } /** * @param mixed $offset * * @throws */ #[ReturnTypeWillChange] public function offsetUnset(mixed $offset): void { if (!isset($this->_attributes[$offset]) && !isset($this->_oldAttributes[$offset])) { return; } unset($this->_attributes[$offset]); unset($this->_oldAttributes[$offset]); } /** * @param string ...$params * * @return array */ public function unset(string ...$params): array { return array_diff_assoc($params, $this->_attributes); } /** * @param array $data * * @return static * @throws */ public static function populate(array $data): static { $model = new static(); $model->_attributes = $data; $model->_oldAttributes = $data; $model->setIsNowExample(); return $model; } /** * @param string $name * @param array $arguments * * @return mixed */ public static function __callStatic(string $name, array $arguments) { return (new static())->{$name}(...$arguments); } /** * @param string $field * * @return array */ public function getOldAttribute(string $field): mixed { return $this->_oldAttributes[$field] ?? NULL; } }