namespace PhalconPlus\Base;
use PhalconPlus\Base\Pagable;
use PhalconPlus\Assert\Assertion as Assert;
use Phalcon\Db\AdapterInterface;
use Phalcon\Mvc\Model\Transaction\Manager as TxManager;
use Phalcon\Mvc\Model\MetaDataInterface;
use Phalcon\Db\AdapterInterface;
use Phalcon\Mvc\Model\Resultset;
use PhalconPlus\Base\Exception as BaseException;
// use Phalcon\Mvc\Model;
// use Phalcon\Mvc\ModelMessage;
// use Phalcon\Db\RawValue;
class Model extends \Phalcon\Mvc\Model
{
// 记录创建时间
public ctime;
// public createdAt;
// 记录更新时间
public mtime;
// public updatedAt;
// 自定义模型唯一键
protected __uniqueKeys = [];
protected optimisticLock = false;
public function initialize()
{
self::setUp([
"notNullValidations" : false,
"castOnHydrate" : true,
"forceCasting" : true
]);
this->useDynamicUpdate(true);
this->keepSnapshots(true);
}
public static function findFirstOrFail(var params = null)
{
var record = static::findFirst(params);
if record == false {
throw new BaseException([
"Recored not exists,",
params
]);
}
return record;
}
public static function findFirstOrEmpty(var params = null)
{
var record = static::findFirst(params);
if record == false {
return new static();
}
return record;
}
public static function findOrFail(var params = null)
{
var collection = static::find(params);
if collection->count() == 0 {
throw new BaseException([
"Collection is empty",
params
]);
}
return collection;
}
public function saveOrFail(var data = null, var whiteList = null) -> boolean
{
var result = this->save(data, whiteList);
if result !== true {
throw new BaseException([
"Model save failed: " . (string) this->getMessage(),
[data, whiteList]
]);
}
return true;
}
public function createOrFail(var data = null, var whiteList = null) -> boolean
{
var result = this->create(data, whiteList);
if result !== true {
throw new BaseException([
"Model create failed: " . (string) this->getMessage(),
[data, whiteList]
]);
}
return true;
}
public function updateOrFail(var data = null, var whiteList = null) -> boolean
{
var result = this->update(data, whiteList);
if result !== true {
throw new BaseException([
"Model update failed: " . (string) this->getMessage(),
[data, whiteList]
]);
}
return true;
}
public function getMessage() -> string | null
{
if count(this->getMessages()) {
return (string) current(this->getMessages());
}
return null;
}
public function getFirstMessage() -> string | null
{
if count(this->getMessages()) {
return (string) reset(this->getMessages());
}
return null;
}
public function getLastMessage() -> string | null
{
if count(this->getMessages()) {
return (string) end(this->getMessages());
}
return null;
}
public function createBuilder(string! alias = "") -> <\Phalcon\Mvc\Model\Query\BuilderInterface>
{
var source;
if !empty alias {
let source = [alias:get_called_class()];
} else {
let source = get_called_class();
}
return this->getModelsManager()->createBuilder()->from(source);
}
public static function newInstance() -> <\Phalcon\Mvc\Model>
{
var className;
let className = get_called_class();
return new {className}();
}
public static function batchInsert(array columns, array rows)
{
var model, conn, e, row;
var columnMap = [], newColumns = [];
var className;
let className = get_called_class();
let model = new {className}();
if method_exists(model, "columnMap") {
let columnMap = array_flip(model->columnMap());
var val;
for val in columns {
if isset(columnMap[val]) {
let newColumns[] = columnMap[val];
}
}
} else {
let newColumns = columns;
}
let conn = model->getWriteConnection();
try {
conn->begin();
for row in rows {
conn->insert(model->getSource(), row, newColumns);
}
conn->commit();
} catch \Exception, e {
conn->rollback();
throw e;
}
return true;
}
public function beforeValidationOnCreate()
{
let this->ctime = date("Y-m-d H:i:s");
let this->mtime = this->ctime;
if property_exists(this, "created_at") {
let this->{"createdAt"} = this->ctime;
}
if property_exists(this, "updated_at") {
let this->{"updatedAt"} = this->ctime;
}
}
public function afterFetch()
{
// nothing
let this->ctime = new \DateTime(this->ctime);
let this->mtime = new \DateTime(this->mtime);
if property_exists(this, "created_at") {
let this->{"createdAt"} = new \DateTime(this->{"createdAt"});
}
if property_exists(this, "updated_at") {
let this->{"updatedAt"} = new \DateTime(this->{"updatedAt"});
}
}
public function beforeCreate()
{
let this->ctime = date("Y-m-d H:i:s");
let this->mtime = this->ctime;
if property_exists(this, "created_at") {
let this->{"createdAt"} = this->ctime;
}
if property_exists(this, "updated_at") {
let this->{"updatedAt"} = this->ctime;
}
}
public function beforeSave()
{
var changedFields;
if this->hasSnapshotData() {
let changedFields = this->getChangedFields();
if !empty changedFields {
let this->mtime = date("Y-m-d H:i:s");
if property_exists(this, "updated_at") {
let this->{"updatedAt"} = this->mtime;
}
}
}
if is_object(this->ctime) && (this->ctime instanceof \DateTime) {
let this->ctime = this->ctime->format("Y-m-d H:i:s");
}
if property_exists(this, "created_at") {
if is_object(this->{"createdAt"}) && (this->{"createdAt"} instanceof \DateTime) {
let this->{"createdAt"} = this->{"createdAt"}->format("Y-m-d H:i:s");
}
}
if is_object(this->mtime) && (this->mtime instanceof \DateTime) {
let this->mtime = this->mtime->format("Y-m-d H:i:s");
}
if property_exists(this, "updated_at") {
if is_object(this->{"updatedAt"}) && (this->{"updatedAt"} instanceof \DateTime) {
let this->{"updatedAt"} = this->{"updatedAt"}->format("Y-m-d H:i:s");
}
}
return true;
}
public function afterSave()
{
if true == this->optimisticLock {
this->resetUniqueFields();
let this->optimisticLock = false;
}
return true;
}
public function toArray(columns = null) -> array
{
var fields, tmp, key, val;
let fields = parent::toArray(columns),
tmp = fields;
for key, val in tmp {
if is_object(val) && (val instanceof \DateTime) {
let fields[key] = val->format("Y-m-d H:i:s");
}
}
return fields;
}
/**
* @alias findByPagable()
*/
public function findByPageable( pagable, array params = [])
{
return this->findByPagable(pagable, params);
}
/**
* find with paginator
* Sorry for the typo with method name
*
* @var array params
* - params["columns"]
* - params["conditions"]
* - params["bind"]
* - params["hydration"]: \Phalcon\Mvc\Model\Resultset::HYDRATE_OBJECTS | HYDRATE_ARRAYS | HYDRATE_RECORDS
*
*/
public function findByPagable( pagable, array params = [])
{
Assert::notNull(pagable, __CLASS__."::".__METHOD__ .": Pagable can not be null");
var builder;
let builder = this->createBuilder();
var val, orderBys = [];
let orderBys = array_map("strval", pagable->getOrderBys());
if !empty orderBys {
// error_log(var_export(orderBys, true));
builder->orderBy(implode(", ", orderBys));
}
if fetch val, params["columns"] {
builder->columns(val);
} else {
builder->columns("*");
}
var bind = [];
if fetch val, params["bind"] {
let bind = val;
}
if fetch val, params["conditions"] {
if empty bind {
builder->where(val);
} else {
builder->where(val, bind);
}
}
var queryBuilder, page;
let queryBuilder = new \Phalcon\Paginator\Adapter\QueryBuilder([
"builder":builder,
"limit":pagable->getPageSize(),
"page":pagable->getPageNo()
]);
let page = queryBuilder->getPaginate();
if typeof page->items == "object" {
var hydration;
if fetch hydration, params["hydration"] {
page->items->setHydrateMode(hydration);
}
}
return new Page(pagable, page->total_items, page->items);
}
/**
* Check if a reord is already exists?
*/
public function exists() -> boolean
{
var metaData, readConnection, schema, source, table, builds, num;
let
metaData = this->getModelsMetaData(),
readConnection = this->getReadConnection(),
schema = this->getSchema(),
source = this->getSource();
if schema {
let table = [schema, source];
} else {
let table = source;
}
if empty this->__uniqueKeys {
if this->_exists(metaData, readConnection, table) {
return true;
}
return false;
}
let builds = this->__buildUniqueCondition(metaData, readConnection);
if empty builds {
return false;
}
let num = readConnection->fetchOne(
"SELECT COUNT(*) \"rowcount\" FROM " . readConnection->escapeIdentifier(table) . " WHERE " . builds["uniqueKey"],
null,
builds["uniqueParams"],
builds["uniqueTypes"]
);
if num["rowcount"] {
return true;
}
return false;
}
/**
* 如果想在更新某条记录的时候额外加入其他条件,可以使用此方法
* where = [
'id > ?', // 特别注意!!! 占位符仅支持?形式,不支持:placeHolder这种形式
'bind' => [
14
]
];
*/
public function setUpdateCondition(array params)
{
var metaData, writeConnection, columnMap, bindDataTypes, primaryKeys, attributeField;
var pk, value, type;
var whereUk = [], uniqueParams = [], uniqueTypes = [];
let metaData = this->getModelsMetaData(),
writeConnection = this->getWriteConnection(),
columnMap = metaData->getColumnMap(this),
bindDataTypes = metaData->getBindTypes(this);
let primaryKeys = metaData->getPrimaryKeyAttributes(this);
for pk in primaryKeys {
/**
* Check if the model has a column map
*/
if typeof columnMap == "array" {
if !fetch attributeField, columnMap[pk] {
throw new BaseException("Model::setUpdateCond: Column '" . pk . "' isn't part of the column map");
}
} else {
let attributeField = pk;
}
if !fetch type, bindDataTypes[pk] {
throw new BaseException("Model::setupdateCond: Column '" . pk . "' isn't part of the table columns");
}
if fetch value, this->{attributeField} {
let uniqueParams[] = value;
} else {
let uniqueParams[] = null;
}
let whereUk[] = writeConnection->escapeIdentifier(pk) . " = ?";
let uniqueTypes[] = type;
}
/**
* Process conditions
*/
var conditions, bind, bindTypes;
if fetch conditions, params[0] {
} else {
if fetch conditions, params["conditions"] {
}
}
if !empty this->_uniqueKey {
let this->_uniqueKey = this->_uniqueKey . " AND ";
}
if typeof conditions == "array" {
merge_append(whereUk, conditions);
let this->_uniqueKey = this->_uniqueKey . join(" AND ", whereUk);
} elseif typeof conditions == "string" {
let conditions = join(" AND ", whereUk) . " AND " . conditions;
let this->_uniqueKey = this->_uniqueKey . conditions;
}
let this->_uniqueKey = str_replace(array_values(columnMap), array_keys(columnMap), this->_uniqueKey);
var countKeys = substr_count(this->_uniqueKey, "= ?");
/**
* Assign bind types
*/
if fetch bind, params["bind"] {
merge_append(uniqueParams, bind);
}
if this->_uniqueParams == null {
let this->_uniqueParams = [];
}
merge_append(this->_uniqueParams, uniqueParams);
let this->_uniqueParams = array_pad(this->_uniqueParams, countKeys, null);
if fetch bindTypes, params["bindTypes"] {
merge_append(uniqueTypes, bindTypes);
}
if this->_uniqueTypes == null {
let this->_uniqueTypes = [];
}
merge_append(this->_uniqueTypes, uniqueTypes);
let this->_uniqueTypes = array_pad(this->_uniqueTypes, countKeys, \Phalcon\Db\Column::BIND_SKIP);
let this->optimisticLock = true;
return this;
}
/**
* columnMap field
*/
public function setUniqueKeys(array whereUk)
{
/**
* field 数据库字段
* attributeField columnMap之后的字段
*/
var field, attributeField, type, metaData, columnMap, bindDataTypes;
let metaData = this->getModelsMetaData();
let columnMap = metaData->getColumnMap(this);
let bindDataTypes = metaData->getBindTypes(this);
for attributeField in whereUk {
if typeof columnMap == "array" {
var tmp;
let tmp = array_flip(columnMap);
if !fetch field, tmp[attributeField] {
throw new BaseException("Model::setUqKeys: Column '" . attributeField . "' isn't part of the column map");
}
} else {
let field = attributeField;
}
let this->__uniqueKeys[attributeField]["field"] = field;
if !fetch type, bindDataTypes[field] {
throw new BaseException("Model::setUqKeys: Column '" . field . "' isn't part of the table columns");
}
let this->__uniqueKeys[attributeField]["type"] = type;
let this->__uniqueKeys[attributeField]["op"] = "=";
}
return this;
}
protected function __buildUniqueCondition( metaData, connection)
{
var value, type, info, field, whereUk, uniqueParams, uniqueTypes, attributeField;
let whereUk = [],
uniqueParams = [],
uniqueTypes = [];
for attributeField, info in this->__uniqueKeys {
let type = info["type"],
field = info["field"],
value = null;
if fetch value, this->{attributeField} {
var selfVal;
if fetch selfVal, info["value"] {
let uniqueParams[] = selfVal;
} else {
let uniqueParams[] = value;
}
} else {
let uniqueParams[] = null;
}
let uniqueTypes[] = type,
whereUk[] = connection->escapeIdentifier(field) . " ".info["op"]." ?";
}
var usefulParams = [];
let usefulParams = array_filter(uniqueParams, function(elem){
return !empty elem; // in zephir, empty means `null`, `empty string` and `empty array`
});
if empty usefulParams { // if no ukField is defined
return false;
}
return [
"uniqueKey" : join(" AND ", whereUk),
"uniqueParams" : uniqueParams,
"uniqueTypes" : uniqueTypes
];
}
public function getUniqueFields() -> array
{
return [
"key" : this->_uniqueKey,
"params" : this->_uniqueParams,
"types" : this->_uniqueTypes
];
}
protected function resetUniqueFields() -> void
{
let this->_uniqueKey = null;
let this->_uniqueParams = null;
let this->_uniqueTypes = null;
}
public function toProtoBuffer(columns = null) ->
{
var proto, toArray, key, val;
let toArray = this->toArray(columns);
let proto = new ProtoBuffer();
for key, val in toArray {
let proto->{key} = is_scalar(val)?val:strval(val);
}
var modelName, manager, relations, referenceModel, referencedEntity, options, alias, lowerAlias;
let modelName = get_class(this), manager = this->getModelsManager();
let relations = manager->getRelations(modelName);
for val in relations {
let referenceModel = val->getReferencedModel();
let referencedEntity = strtolower(referenceModel);
let options = val->getOptions();
if fetch alias, options["alias"] {
if typeof alias != "string" {
throw new BaseException("Relation alias must be a string");
}
let lowerAlias = strtolower(alias);
} else {
let lowerAlias = referencedEntity;
}
var method, property;
let method = "get".alias;
let property = lcfirst(alias);
let proto->{property} = this->{method}()->toArray();
}
return proto;
}
/**
* Gets the connection used to read data for the model
*
* Check transaction in writeConnection before `_transaction`
*/
public function getReadConnection() ->
{
var dbConn;
let dbConn = this->getWriteConnection();
if dbConn->isUnderTransaction() {
return dbConn;
} else {
return parent::getReadConnection();
}
}
}