Skip to content

Commit 7e21423

Browse files
committed
Fix #16411, added ORM SessionCache AKA FirstLevelCache
1 parent 450009c commit 7e21423

File tree

12 files changed

+424
-44
lines changed

12 files changed

+424
-44
lines changed

config.json

+4
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,10 @@
153153
"type": "bool",
154154
"default": true
155155
},
156+
"orm.session_cache": {
157+
"type": "bool",
158+
"default": false
159+
},
156160
"warning.enable": {
157161
"type": "bool",
158162
"default": true

phalcon/Mvc/Model.zep

+41-1
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,11 @@ abstract class Model extends AbstractInjectionAware implements EntityInterface,
163163
*/
164164
protected uniqueTypes = [];
165165

166+
/**
167+
* @var string
168+
*/
169+
protected modelUUID;
170+
166171
/**
167172
* Phalcon\Mvc\Model constructor
168173
*/
@@ -5992,4 +5997,39 @@ abstract class Model extends AbstractInjectionAware implements EntityInterface,
59925997
}
59935998
}
59945999
}
5995-
}
6000+
6001+
6002+
/**
6003+
* set the model UUID for session cache
6004+
*
6005+
* @var string uuid
6006+
* @return void
6007+
*/
6008+
public function setModelUUID(string uuid) -> void
6009+
{
6010+
let this->modelUUID = uuid;
6011+
}
6012+
6013+
/**
6014+
* get the model UUID for session cache
6015+
*
6016+
* @return string
6017+
*/
6018+
public function getModelUUID() -> string
6019+
{
6020+
return this->modelUUID;
6021+
}
6022+
6023+
/**
6024+
* Used to destroy reference in WeakCache
6025+
*
6026+
* @return void
6027+
*/
6028+
public function __destruct()
6029+
{
6030+
if true === globals_get("orm.session_cache") {
6031+
this->modelsManager->getSessionCache()->delete(this->modelUUID);
6032+
}
6033+
}
6034+
6035+
}

phalcon/Mvc/Model/Manager.zep

+34
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ use Phalcon\Mvc\ModelInterface;
1919
use Phalcon\Mvc\Model\Query\Builder;
2020
use Phalcon\Mvc\Model\Query\BuilderInterface;
2121
use Phalcon\Mvc\Model\Query\StatusInterface;
22+
use Phalcon\Storage\Adapter\AbstractAdapter;
2223
use ReflectionClass;
2324
use ReflectionProperty;
2425

@@ -221,6 +222,13 @@ class Manager implements ManagerInterface, InjectionAwareInterface, EventsAwareI
221222
*/
222223
protected reusable = [];
223224

225+
/**
226+
* Thread cache.
227+
*
228+
* @var AbstractAdapter|null
229+
*/
230+
protected sessionCache = null;
231+
224232
/**
225233
* Destroys the current PHQL cache
226234
*/
@@ -2377,4 +2385,30 @@ class Manager implements ManagerInterface, InjectionAwareInterface, EventsAwareI
23772385

23782386
return isset this->{collection}[keyRelation];
23792387
}
2388+
2389+
/**
2390+
* Sets a cache for model working in memory
2391+
*/
2392+
public function hasSessionCache() -> bool
2393+
{
2394+
return this->sessionCache !== null;
2395+
}
2396+
2397+
2398+
/**
2399+
* Sets a cache for model working in memory
2400+
*/
2401+
public function setSessionCache(<AbstractAdapter> cache) -> void
2402+
{
2403+
let this->sessionCache = cache;
2404+
}
2405+
2406+
/**
2407+
* Returns a cache instance or null if not configured
2408+
*/
2409+
public function getSessionCache() -> <AbstractAdapter> | null
2410+
{
2411+
return this->sessionCache;
2412+
}
2413+
23802414
}

phalcon/Mvc/Model/MetaData.zep

+30-3
Original file line numberDiff line numberDiff line change
@@ -924,8 +924,8 @@ abstract class MetaData implements InjectionAwareInterface, MetaDataInterface
924924
*
925925
* @return string
926926
*/
927-
public final function getColumnMapUniqueKey(<ModelInterface> model) -> string | null
928-
{
927+
public final function getColumnMapUniqueKey(<ModelInterface> model) -> string | null
928+
{
929929
string key;
930930
let key = get_class_lower(model);
931931
if false === isset(this->columnMap[key]) {
@@ -934,5 +934,32 @@ abstract class MetaData implements InjectionAwareInterface, MetaDataInterface
934934
}
935935
}
936936
return key;
937-
}
937+
}
938+
939+
/**
940+
* Returns the model UniqueID based on model and array row primary key(s) value(s)
941+
*/
942+
public function getModelUUID(<ModelInterface> model, array row) -> string | null
943+
{
944+
var pk, pks;
945+
string uuid;
946+
let pks = this->readMetaDataIndex(model, self::MODELS_PRIMARY_KEY);
947+
if null === pks {
948+
return null;
949+
}
950+
let uuid = get_class(model);
951+
952+
for pk in pks {
953+
let uuid = uuid . ":" . row[pk];
954+
}
955+
return uuid;
956+
}
957+
958+
/**
959+
* Compares if two models are the same in memory
960+
*/
961+
public function modelEquals(<ModelInterface> first, <ModelInterface> other) -> bool
962+
{
963+
return spl_object_id(first) === spl_object_id(other);
964+
}
938965
}

phalcon/Mvc/Model/Query.zep

+6-2
Original file line numberDiff line numberDiff line change
@@ -1334,7 +1334,9 @@ class Query implements QueryInterface, InjectionAwareInterface
13341334
resultObject,
13351335
resultData,
13361336
cache,
1337-
isKeepingSnapshots
1337+
isKeepingSnapshots,
1338+
manager,
1339+
metaData
13381340
);
13391341
}
13401342

@@ -1344,7 +1346,9 @@ class Query implements QueryInterface, InjectionAwareInterface
13441346
return new Complex(
13451347
columns1,
13461348
resultData,
1347-
cache
1349+
cache,
1350+
manager,
1351+
metaData
13481352
);
13491353
}
13501354

phalcon/Mvc/Model/Resultset.zep

+28-2
Original file line numberDiff line numberDiff line change
@@ -126,13 +126,31 @@ abstract class Resultset
126126
*/
127127
protected result;
128128

129+
/**
130+
* @var Phalcon\Mvc\Model\Manager|null
131+
*/
132+
protected manager = null;
133+
134+
135+
/**
136+
* @var Phalcon\Mvc\Model\Metadata|null
137+
*/
138+
protected metaData = null;
139+
140+
/**
141+
* Thread cache.
142+
*
143+
* @var Phalcon\Session\Adapter\AbstractAdapter|null
144+
*/
145+
protected sessionCache = null;
146+
129147
/**
130148
* Phalcon\Mvc\Model\Resultset constructor
131149
*
132150
* @param ResultInterface|false $result
133151
* @param mixed|null $cache
134152
*/
135-
public function __construct(var result, var cache = null)
153+
public function __construct(var result, var cache = null, manager = null, metaData = null)
136154
{
137155
var prefetchRecords, rowCount, rows;
138156

@@ -145,7 +163,15 @@ abstract class Resultset
145163

146164
return;
147165
}
148-
166+
if true === globals_get("orm.session_cache") {
167+
if null !== manager {
168+
let this->manager = manager;
169+
let this->sessionCache = manager->getSessionCache();
170+
}
171+
if null !== metaData {
172+
let this->metaData = metaData;
173+
}
174+
}
149175
/**
150176
* Valid resultsets are Phalcon\Db\ResultInterface instances
151177
*/

phalcon/Mvc/Model/Resultset/Complex.zep

+45-28
Original file line numberDiff line numberDiff line change
@@ -53,15 +53,17 @@ class Complex extends Resultset implements ResultsetInterface
5353
public function __construct(
5454
var columnTypes,
5555
<ResultInterface> result = null,
56-
var cache = null
56+
var cache = null,
57+
manager = null,
58+
metaData = null
5759
)
5860
{
5961
/**
6062
* Column types, tell the resultset how to build the result
6163
*/
6264
let this->columnTypes = columnTypes;
6365

64-
parent::__construct(result, cache);
66+
parent::__construct(result, cache, manager, metaData);
6567
}
6668

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

7678
let activeRow = this->activeRow;
7779

@@ -170,35 +172,50 @@ class Complex extends Resultset implements ResultsetInterface
170172
if !fetch keepSnapshots, column["keepSnapshots"] {
171173
let keepSnapshots = false;
172174
}
175+
/**
176+
* checks for session cache and returns already in memory models
177+
*/
178+
let value = null;
179+
if true === globals_get("orm.session_cache") {
180+
let modelName = get_class(column["instance"]);
181+
let model = new {modelName}();
182+
let uuid = this->metaData->getModelUUID(model, row);
183+
let value = this->sessionCache->get(uuid);
184+
}
173185

174-
if globals_get("orm.late_state_binding") {
175-
if column["instance"] instanceof Model {
176-
let modelName = get_class(column["instance"]);
186+
if null === value {
187+
if globals_get("orm.late_state_binding") {
188+
if column["instance"] instanceof Model {
189+
let modelName = get_class(column["instance"]);
190+
} else {
191+
let modelName = "Phalcon\\Mvc\\Model";
192+
}
193+
194+
let value = {modelName}::cloneResultMap(
195+
column["instance"],
196+
rowModel,
197+
columnMap,
198+
dirtyState,
199+
keepSnapshots
200+
);
177201
} else {
178-
let modelName = "Phalcon\\Mvc\\Model";
202+
/**
203+
* Get the base instance. Assign the values to the
204+
* attributes using a column map
205+
*/
206+
let value = Model::cloneResultMap(
207+
column["instance"],
208+
rowModel,
209+
columnMap,
210+
dirtyState,
211+
keepSnapshots
212+
);
213+
}
214+
if true === globals_get("orm.session_cache") {
215+
this->sessionCache->set(uuid, value);
216+
value->setModelUUID(uuid);
179217
}
180-
181-
let value = {modelName}::cloneResultMap(
182-
column["instance"],
183-
rowModel,
184-
columnMap,
185-
dirtyState,
186-
keepSnapshots
187-
);
188-
} else {
189-
/**
190-
* Get the base instance. Assign the values to the
191-
* attributes using a column map
192-
*/
193-
let value = Model::cloneResultMap(
194-
column["instance"],
195-
rowModel,
196-
columnMap,
197-
dirtyState,
198-
keepSnapshots
199-
);
200218
}
201-
202219
break;
203220

204221
default:

phalcon/Mvc/Model/Resultset/Simple.zep

+21-4
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,9 @@ class Simple extends Resultset
5656
var model,
5757
result,
5858
var cache = null,
59-
bool keepSnapshots = false
59+
bool keepSnapshots = false,
60+
manager = null,
61+
metaData = null
6062
)
6163
{
6264
let this->model = model,
@@ -66,15 +68,15 @@ class Simple extends Resultset
6668
*/
6769
let this->keepSnapshots = keepSnapshots;
6870

69-
parent::__construct(result, cache);
71+
parent::__construct(result, cache, manager, metaData);
7072
}
7173

7274
/**
7375
* Returns current row in the resultset
7476
*/
7577
final public function current() -> <ModelInterface> | null
7678
{
77-
var row, hydrateMode, columnMap, activeRow, modelName;
79+
var row, hydrateMode, columnMap, activeRow, modelName, uuid;
7880

7981
let activeRow = this->activeRow;
8082

@@ -111,6 +113,18 @@ class Simple extends Resultset
111113
*/
112114
switch hydrateMode {
113115
case Resultset::HYDRATE_RECORDS:
116+
/**
117+
* checks for session cache and returns already in memory models
118+
*/
119+
if true === globals_get("orm.session_cache") {
120+
let uuid = this->metaData->getModelUUID(this->model, row);
121+
let activeRow = this->sessionCache->get(uuid);
122+
if null !== activeRow {
123+
let this->activeRow = activeRow;
124+
return activeRow;
125+
}
126+
}
127+
114128
/**
115129
* Set records as dirty state PERSISTENT by default
116130
* Performs the standard hydration based on objects
@@ -138,7 +152,10 @@ class Simple extends Resultset
138152
this->keepSnapshots
139153
);
140154
}
141-
155+
if true === globals_get("orm.session_cache") {
156+
this->sessionCache->set(uuid, activeRow);
157+
activeRow->setModelUUID(uuid);
158+
}
142159
break;
143160

144161
default:

0 commit comments

Comments
 (0)