Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix #16411, added ORM SessionCache AKA FirstLevelCache #16450

Open
wants to merge 1 commit into
base: 5.0.x
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading
Loading