Skip to content

Commit 736c049

Browse files
committed
Fix #16029, added ability to save Multiple fields
1 parent d5a12bf commit 736c049

File tree

16 files changed

+912
-134
lines changed

16 files changed

+912
-134
lines changed

CHANGELOG-5.0.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
# Changelog
22

3-
## [5.3.1](https://github.com/phalcon/cphalcon/releases/tag/v5.3.1) (xxxx-xx-xx)
3+
## [5.4.0](https://github.com/phalcon/cphalcon/releases/tag/v5.4.0) (xxxx-xx-xx)
4+
5+
### Added
6+
7+
- Added `Phalcon\Mvc\Model::setRelated()` to allow setting related models and automaticly de added to the dirtyRelated list [#16222] (https://github.com/phalcon/cphalcon/issues/16222)
48

59
### Fixed
10+
611
- Infinite save loop in Model::save() [#16395](https://github.com/phalcon/cphalcon/issues/16395)
712

813

phalcon/Mvc/Model.zep

Lines changed: 115 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -4996,8 +4996,8 @@ abstract class Model extends AbstractInjectionAware implements EntityInterface,
49964996

49974997
protected function preSaveRelatedRecords(<AdapterInterface> connection, related, <CollectionInterface> visited) -> bool
49984998
{
4999-
var className, manager, type, relation, columns, referencedFields, nesting, name, record;
5000-
4999+
var className, manager, type, relation, columns, referencedFields, nesting, name, record, columnA, columnB;
5000+
int columnCount, i;
50015001
let nesting = false;
50025002

50035003
/**
@@ -5034,17 +5034,6 @@ abstract class Model extends AbstractInjectionAware implements EntityInterface,
50345034
"Only objects can be stored as part of belongs-to relations in '" . get_class(this) . "' Relation " . name
50355035
);
50365036
}
5037-
let columns = relation->getFields(),
5038-
referencedFields = relation->getReferencedFields();
5039-
// let columns = relation->getFields(),
5040-
// referencedModel = relation->getReferencedModel(),
5041-
// referencedFields = relation->getReferencedFields();
5042-
5043-
if unlikely typeof columns === "array" {
5044-
connection->rollback(nesting);
5045-
5046-
throw new Exception("Not implemented in '" . get_class(this) . "' Relation " . name);
5047-
}
50485037

50495038
/**
50505039
* If dynamic update is enabled, saving the record must not take any action
@@ -5069,7 +5058,18 @@ abstract class Model extends AbstractInjectionAware implements EntityInterface,
50695058
* Read the attribute from the referenced model and assign
50705059
* it to the current model
50715060
*/
5072-
let this->{columns} = record->readAttribute(referencedFields);
5061+
let columns = relation->getFields(),
5062+
referencedFields = relation->getReferencedFields();
5063+
if unlikely typeof columns === "array" {
5064+
let columnCount = count(columns) - 1;
5065+
for i in range(0, columnCount) {
5066+
let columnA = columns[i];
5067+
let columnB = referencedFields[i];
5068+
let this->{columnA} = record->{columnB};
5069+
}
5070+
} else {
5071+
let this->{columns} = record->{referencedFields};
5072+
}
50735073
}
50745074
}
50755075
}
@@ -5105,11 +5105,14 @@ abstract class Model extends AbstractInjectionAware implements EntityInterface,
51055105
protected function postSaveRelatedRecords(<AdapterInterface> connection, related, <CollectionInterface> visited) -> bool
51065106
{
51075107
var nesting, className, manager, relation, name, record,
5108-
columns, referencedModel, referencedFields, relatedRecords, value,
5108+
columns, referencedModel, referencedFields, relatedRecords,
51095109
recordAfter, intermediateModel, intermediateFields,
5110-
intermediateValue, intermediateModelName,
5111-
intermediateReferencedFields, existingIntermediateModel;
5110+
intermediateModelName,
5111+
intermediateReferencedFields, existingIntermediateModel, columnA, columnB;
51125112
bool isThrough;
5113+
int columnCount, referencedFieldsCount, i;
5114+
string intermediateConditions;
5115+
array conditions, placeholders;
51135116

51145117
let nesting = false,
51155118
className = get_class(this),
@@ -5144,12 +5147,6 @@ abstract class Model extends AbstractInjectionAware implements EntityInterface,
51445147
referencedModel = relation->getReferencedModel(),
51455148
referencedFields = relation->getReferencedFields();
51465149

5147-
if unlikely typeof columns === "array" {
5148-
connection->rollback(nesting);
5149-
5150-
throw new Exception("Not implemented in '" . className . "' on Relation " . name);
5151-
}
5152-
51535150
/**
51545151
* Create an implicit array for has-many/has-one records
51555152
*/
@@ -5159,18 +5156,6 @@ abstract class Model extends AbstractInjectionAware implements EntityInterface,
51595156
let relatedRecords = record;
51605157
}
51615158

5162-
if unlikely !fetch value, this->{columns} {
5163-
connection->rollback(nesting);
5164-
5165-
throw new Exception(
5166-
"The column '" . columns . "' needs to be present in the model '" . className . "'"
5167-
);
5168-
}
5169-
5170-
/**
5171-
* Get the value of the field from the current model
5172-
* Check if the relation is a has-many-to-many
5173-
*/
51745159
let isThrough = (bool) relation->isThrough();
51755160

51765161
/**
@@ -5180,7 +5165,30 @@ abstract class Model extends AbstractInjectionAware implements EntityInterface,
51805165
let intermediateModelName = relation->getIntermediateModel(),
51815166
intermediateFields = relation->getIntermediateFields(),
51825167
intermediateReferencedFields = relation->getIntermediateReferencedFields();
5183-
5168+
if unlikely typeof columns === "array" {
5169+
let columnCount = count(columns) - 1;
5170+
if relation->getType() == Relation::HAS_ONE_THROUGH {
5171+
let placeholders = [];
5172+
let conditions = [];
5173+
for i in range(0, columnCount) {
5174+
let columnA = columns[i];
5175+
let conditions[] = "[". intermediateFields[i] . "] = :APR" . i . ":",
5176+
placeholders["APR" . i] = this->{columnA};
5177+
}
5178+
let intermediateConditions = join(" AND ", conditions);
5179+
}
5180+
} else {
5181+
if relation->getType() == Relation::HAS_ONE_THROUGH {
5182+
let placeholders = [];
5183+
let intermediateConditions = "[" . intermediateFields . "] = ?0";
5184+
let placeholders[] = this->{columns};
5185+
}
5186+
}
5187+
if unlikely typeof referencedFields === "array" {
5188+
let referencedFieldsCount = count(referencedFields) - 1;
5189+
} else {
5190+
let referencedFieldsCount = null;
5191+
}
51845192
for recordAfter in relatedRecords {
51855193
/**
51865194
* Save the record and get messages
@@ -5213,38 +5221,39 @@ abstract class Model extends AbstractInjectionAware implements EntityInterface,
52135221
if relation->getType() == Relation::HAS_ONE_THROUGH {
52145222
let existingIntermediateModel = intermediateModel->findFirst(
52155223
[
5216-
"[" . intermediateFields . "] = ?0",
5217-
"bind": [value]
5224+
intermediateConditions,
5225+
"bind": placeholders
52185226
]
52195227
);
52205228

52215229
if existingIntermediateModel {
52225230
let intermediateModel = existingIntermediateModel;
52235231
}
52245232
}
5225-
5226-
/**
5227-
* Write value in the intermediate model
5228-
*/
5229-
intermediateModel->writeAttribute(
5230-
intermediateFields,
5231-
value
5232-
);
5233-
5234-
/**
5235-
* Get the value from the referenced model
5236-
*/
5237-
let intermediateValue = recordAfter->readAttribute(
5238-
referencedFields
5239-
);
5240-
5241-
/**
5242-
* Write the intermediate value in the intermediate model
5243-
*/
5244-
intermediateModel->writeAttribute(
5245-
intermediateReferencedFields,
5246-
intermediateValue
5247-
);
5233+
if unlikely typeof columns === "array" {
5234+
for i in range(0, columnCount) {
5235+
let columnA = columns[i];
5236+
let columnB = intermediateFields[i];
5237+
let intermediateModel->{columnB} = this->{columnA};
5238+
}
5239+
} else {
5240+
/**
5241+
* Write value in the intermediate model
5242+
*/
5243+
let intermediateModel->{intermediateFields} = this->{columns};
5244+
}
5245+
if unlikely typeof referencedFields === "array" {
5246+
for i in range(0, referencedFieldsCount) {
5247+
let columnA = referencedFields[i];
5248+
let columnB = intermediateReferencedFields[i];
5249+
let intermediateModel->{columnB} = recordAfter->{columnA};
5250+
}
5251+
} else {
5252+
/**
5253+
* Write the intermediate value in the intermediate model
5254+
*/
5255+
let intermediateModel->{intermediateReferencedFields} = recordAfter->{referencedFields};
5256+
}
52485257

52495258
/**
52505259
* Save the record and get messages
@@ -5264,27 +5273,56 @@ abstract class Model extends AbstractInjectionAware implements EntityInterface,
52645273
}
52655274
}
52665275
} else {
5267-
for recordAfter in relatedRecords {
5268-
/**
5269-
* Assign the value to the
5270-
*/
5271-
recordAfter->writeAttribute(referencedFields, value);
5272-
/**
5273-
* Save the record and get messages
5274-
*/
5275-
if !recordAfter->doSave(visited) {
5276+
if unlikely typeof columns === "array" {
5277+
let columnCount = count(columns) - 1;
5278+
for recordAfter in relatedRecords {
5279+
for i in range(0, columnCount) {
5280+
let columnA = columns[i];
5281+
let columnB = referencedFields[i];
5282+
let recordAfter->{columnB} = this->{columnA};
5283+
}
52765284
/**
5277-
* Get the validation messages generated by the
5278-
* referenced model
5285+
* Save the record and get messages
52795286
*/
5280-
this->appendMessagesFrom(recordAfter);
5281-
5287+
if !recordAfter->doSave(visited) {
5288+
/**
5289+
* Get the validation messages generated by the
5290+
* referenced model
5291+
*/
5292+
this->appendMessagesFrom(recordAfter);
5293+
5294+
/**
5295+
* Rollback the implicit transaction
5296+
*/
5297+
connection->rollback(nesting);
5298+
5299+
return false;
5300+
}
5301+
}
5302+
} else {
5303+
for recordAfter in relatedRecords {
52825304
/**
5283-
* Rollback the implicit transaction
5305+
* Assign the value to the
52845306
*/
5285-
connection->rollback(nesting);
5307+
let recordAfter->{referencedFields} = this->{columns};
5308+
/**
5309+
* Save the record and get messages
5310+
*/
5311+
if !recordAfter->doSave(visited) {
52865312

5287-
return false;
5313+
/**
5314+
* Get the validation messages generated by the
5315+
* referenced model
5316+
*/
5317+
this->appendMessagesFrom(recordAfter);
5318+
5319+
/**
5320+
* Rollback the implicit transaction
5321+
*/
5322+
connection->rollback(nesting);
5323+
5324+
return false;
5325+
}
52885326
}
52895327
}
52905328
}

0 commit comments

Comments
 (0)