Skip to content

Commit

Permalink
Fix #16411, added ORM SessionCache AKA FirstLevelCache
Browse files Browse the repository at this point in the history
  • Loading branch information
rudiservo committed Oct 2, 2023
1 parent 450009c commit ddbcbe2
Show file tree
Hide file tree
Showing 12 changed files with 424 additions and 44 deletions.
4 changes: 4 additions & 0 deletions config.json
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,10 @@
"type": "bool",
"default": true
},
"orm.session_cache": {
"type": "bool",
"default": false
},
"warning.enable": {
"type": "bool",
"default": true
Expand Down
42 changes: 41 additions & 1 deletion phalcon/Mvc/Model.zep
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,11 @@ abstract class Model extends AbstractInjectionAware implements EntityInterface,
*/
protected uniqueTypes = [];

/**
* @var string
*/
protected modelUUID;

/**
* Phalcon\Mvc\Model constructor
*/
Expand Down Expand Up @@ -5992,4 +5997,39 @@ abstract class Model extends AbstractInjectionAware implements EntityInterface,
}
}
}
}


/**
* set the model UUID for session cache
*
* @var string uuid
* @return void
*/
public function setModelUUID(string uuid) -> void
{
let this->modelUUID = uuid;
}

/**
* get the model UUID for session cache
*
* @return string
*/
public function getModelUUID() -> string
{
return this->modelUUID;
}

/**
* Used to destroy reference in WeakCache
*
* @return void
*/
public function __destruct()
{
if true === globals_get("orm.session_cache") {
this->modelsManager->getSessionCache()->delete(this->modelUUID);
}
}

}
34 changes: 34 additions & 0 deletions phalcon/Mvc/Model/Manager.zep
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ use Phalcon\Mvc\ModelInterface;
use Phalcon\Mvc\Model\Query\Builder;
use Phalcon\Mvc\Model\Query\BuilderInterface;
use Phalcon\Mvc\Model\Query\StatusInterface;
use Phalcon\Storage\Adapter\AbstractAdapter;
use ReflectionClass;
use ReflectionProperty;

Expand Down Expand Up @@ -221,6 +222,13 @@ class Manager implements ManagerInterface, InjectionAwareInterface, EventsAwareI
*/
protected reusable = [];

/**
* Thread cache.
*
* @var AbstractAdapter|null
*/
protected sessionCache = null;

/**
* Destroys the current PHQL cache
*/
Expand Down Expand Up @@ -2377,4 +2385,30 @@ class Manager implements ManagerInterface, InjectionAwareInterface, EventsAwareI

return isset this->{collection}[keyRelation];
}

/**
* Sets a cache for model working in memory
*/
public function hasSessionCache() -> bool
{
return this->sessionCache !== null;
}


/**
* Sets a cache for model working in memory
*/
public function setSessionCache(<AbstractAdapter> cache) -> void
{
let this->sessionCache = cache;
}

/**
* Returns a cache instance or null if not configured
*/
public function getSessionCache() -> <AbstractAdapter> | null
{
return this->sessionCache;
}

}
33 changes: 30 additions & 3 deletions phalcon/Mvc/Model/MetaData.zep
Original file line number Diff line number Diff line change
Expand Up @@ -924,8 +924,8 @@ abstract class MetaData implements InjectionAwareInterface, MetaDataInterface
*
* @return string
*/
public final function getColumnMapUniqueKey(<ModelInterface> model) -> string | null
{
public final function getColumnMapUniqueKey(<ModelInterface> model) -> string | null
{
string key;
let key = get_class_lower(model);
if false === isset(this->columnMap[key]) {
Expand All @@ -934,5 +934,32 @@ abstract class MetaData implements InjectionAwareInterface, MetaDataInterface
}
}
return key;
}
}

/**
* Returns the model UniqueID based on model and array row primary key(s) value(s)
*/
public function getModelUUID(<ModelInterface> model, array row) -> string | null
{
var pk, pks;
string uuid;
let pks = this->readMetaDataIndex(model, self::MODELS_PRIMARY_KEY);
if null === pks {
return null;
}
let uuid = get_class(model);

for pk in pks {
let uuid = uuid . ":" . row[pk];
}
return uuid;
}

/**
* Compares if two models are the same in memory
*/
public function modelEquals(<ModelInterface> first, <ModelInterface> other) -> bool
{
return spl_object_id(first) === spl_object_id(other);
}
}
8 changes: 6 additions & 2 deletions phalcon/Mvc/Model/Query.zep
Original file line number Diff line number Diff line change
Expand Up @@ -1334,7 +1334,9 @@ class Query implements QueryInterface, InjectionAwareInterface
resultObject,
resultData,
cache,
isKeepingSnapshots
isKeepingSnapshots,
manager,
metaData
);
}

Expand All @@ -1344,7 +1346,9 @@ class Query implements QueryInterface, InjectionAwareInterface
return new Complex(
columns1,
resultData,
cache
cache,
manager,
metaData
);
}

Expand Down
30 changes: 28 additions & 2 deletions phalcon/Mvc/Model/Resultset.zep
Original file line number Diff line number Diff line change
Expand Up @@ -126,13 +126,31 @@ abstract class Resultset
*/
protected result;

/**
* @var \Phalcon\Mvc\Model\Manager|null
*/
protected manager = null;


/**
* @var \Phalcon\Mvc\Model\Metadata|null
*/
protected metaData = null;

/**
* Thread cache.
*
* @var \Phalcon\Session\Adapter\AbstractAdapter|null
*/
protected sessionCache = null;

/**
* Phalcon\Mvc\Model\Resultset constructor
*
* @param ResultInterface|false $result
* @param mixed|null $cache
*/
public function __construct(var result, var cache = null)
public function __construct(var result, var cache = null, manager = null, metaData = null)
{
var prefetchRecords, rowCount, rows;

Expand All @@ -145,7 +163,15 @@ abstract class Resultset

return;
}

if true === globals_get("orm.session_cache") {
if null !== manager {
let this->manager = manager;
let this->sessionCache = manager->getSessionCache();
}
if null !== metaData {
let this->metaData = metaData;
}
}
/**
* Valid resultsets are Phalcon\Db\ResultInterface instances
*/
Expand Down
73 changes: 45 additions & 28 deletions phalcon/Mvc/Model/Resultset/Complex.zep
Original file line number Diff line number Diff line change
Expand Up @@ -53,15 +53,17 @@ class Complex extends Resultset implements ResultsetInterface
public function __construct(
var columnTypes,
<ResultInterface> result = null,
var cache = null
var cache = null,
manager = null,
metaData = null
)
{
/**
* Column types, tell the resultset how to build the result
*/
let this->columnTypes = columnTypes;

parent::__construct(result, cache);
parent::__construct(result, cache, manager, metaData);
}

/**
Expand All @@ -71,7 +73,7 @@ class Complex extends Resultset implements ResultsetInterface
{
var row, hydrateMode, eager, dirtyState, alias, activeRow, type, column,
columnValue, value, attribute, source, attributes, columnMap,
rowModel, keepSnapshots, sqlAlias, modelName;
rowModel, keepSnapshots, sqlAlias, modelName, uuid, model;

let activeRow = this->activeRow;

Expand Down Expand Up @@ -170,35 +172,50 @@ class Complex extends Resultset implements ResultsetInterface
if !fetch keepSnapshots, column["keepSnapshots"] {
let keepSnapshots = false;
}
/**
* checks for session cache and returns already in memory models
*/
let value = null;
if true === globals_get("orm.session_cache") {
let modelName = get_class(column["instance"]);
let model = new {modelName}();
let uuid = this->metaData->getModelUUID(model, row);
let value = this->sessionCache->get(uuid);
}

if globals_get("orm.late_state_binding") {
if column["instance"] instanceof Model {
let modelName = get_class(column["instance"]);
if null === value {
if globals_get("orm.late_state_binding") {
if column["instance"] instanceof Model {
let modelName = get_class(column["instance"]);
} else {
let modelName = "Phalcon\\Mvc\\Model";
}

let value = {modelName}::cloneResultMap(
column["instance"],
rowModel,
columnMap,
dirtyState,
keepSnapshots
);
} else {
let modelName = "Phalcon\\Mvc\\Model";
/**
* Get the base instance. Assign the values to the
* attributes using a column map
*/
let value = Model::cloneResultMap(
column["instance"],
rowModel,
columnMap,
dirtyState,
keepSnapshots
);
}
if true === globals_get("orm.session_cache") {
this->sessionCache->set(uuid, value);
value->setModelUUID(uuid);
}

let value = {modelName}::cloneResultMap(
column["instance"],
rowModel,
columnMap,
dirtyState,
keepSnapshots
);
} else {
/**
* Get the base instance. Assign the values to the
* attributes using a column map
*/
let value = Model::cloneResultMap(
column["instance"],
rowModel,
columnMap,
dirtyState,
keepSnapshots
);
}

break;

default:
Expand Down
25 changes: 21 additions & 4 deletions phalcon/Mvc/Model/Resultset/Simple.zep
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,9 @@ class Simple extends Resultset
var model,
result,
var cache = null,
bool keepSnapshots = false
bool keepSnapshots = false,
manager = null,
metaData = null
)
{
let this->model = model,
Expand All @@ -66,15 +68,15 @@ class Simple extends Resultset
*/
let this->keepSnapshots = keepSnapshots;

parent::__construct(result, cache);
parent::__construct(result, cache, manager, metaData);
}

/**
* Returns current row in the resultset
*/
final public function current() -> <ModelInterface> | null
{
var row, hydrateMode, columnMap, activeRow, modelName;
var row, hydrateMode, columnMap, activeRow, modelName, uuid;

let activeRow = this->activeRow;

Expand Down Expand Up @@ -111,6 +113,18 @@ class Simple extends Resultset
*/
switch hydrateMode {
case Resultset::HYDRATE_RECORDS:
/**
* checks for session cache and returns already in memory models
*/
if true === globals_get("orm.session_cache") {
let uuid = this->metaData->getModelUUID(this->model, row);
let activeRow = this->sessionCache->get(uuid);
if null !== activeRow {
let this->activeRow = activeRow;
return activeRow;
}
}

/**
* Set records as dirty state PERSISTENT by default
* Performs the standard hydration based on objects
Expand Down Expand Up @@ -138,7 +152,10 @@ class Simple extends Resultset
this->keepSnapshots
);
}

if true === globals_get("orm.session_cache") {
this->sessionCache->set(uuid, activeRow);
activeRow->setModelUUID(uuid);
}
break;

default:
Expand Down

0 comments on commit ddbcbe2

Please sign in to comment.