Skip to content

Commit 8da39a8

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

20 files changed

+1311
-150
lines changed

CHANGELOG-5.0.md

+6-1
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

+136-86
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, j, t;
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,36 @@ abstract class Model extends AbstractInjectionAware implements EntityInterface,
51805165
let intermediateModelName = relation->getIntermediateModel(),
51815166
intermediateFields = relation->getIntermediateFields(),
51825167
intermediateReferencedFields = relation->getIntermediateReferencedFields();
5168+
let placeholders = [];
5169+
let conditions = [];
51835170

5171+
/**
5172+
* Always check for existing intermediate models
5173+
* otherwise conflicts will arise on insert instead of update
5174+
*/
5175+
if unlikely typeof columns === "array" {
5176+
let columnCount = count(columns) - 1;
5177+
for i in range(0, columnCount) {
5178+
let columnA = columns[i];
5179+
let conditions[] = "[". intermediateFields[i] . "] = :APR" . i . ":",
5180+
placeholders["APR" . i] = this->{columnA};
5181+
}
5182+
let i = columnCount + 1;
5183+
} else {
5184+
let conditions[] = "[" . intermediateFields . "] = :APR0:";
5185+
let placeholders["APR0"] = this->{columns};
5186+
let i = 1;
5187+
}
5188+
if unlikely typeof referencedFields === "array" {
5189+
let referencedFieldsCount = count(referencedFields) - 1;
5190+
for j in range(0, referencedFieldsCount) {
5191+
let t = j + i;
5192+
let conditions[] = "[". intermediateReferencedFields[j] . "] = :APR" . t . ":";
5193+
}
5194+
} else {
5195+
let conditions[] = "[". intermediateReferencedFields . "] = :APR" . i . ":";
5196+
}
5197+
let intermediateConditions = join(" AND ", conditions);
51845198
for recordAfter in relatedRecords {
51855199
/**
51865200
* Save the record and get messages
@@ -5191,14 +5205,23 @@ abstract class Model extends AbstractInjectionAware implements EntityInterface,
51915205
* referenced model
51925206
*/
51935207
this->appendMessagesFrom(recordAfter);
5194-
5208+
51955209
/**
51965210
* Rollback the implicit transaction
51975211
*/
51985212
connection->rollback(nesting);
5199-
5213+
52005214
return false;
52015215
}
5216+
if unlikely typeof referencedFields === "array" {
5217+
for j in range(0, referencedFieldsCount) {
5218+
let columnA = referencedFields[j];
5219+
let t = j + i;
5220+
let placeholders["APR" . t] = recordAfter->{columnA};
5221+
}
5222+
} else {
5223+
let placeholders["APR" . i] = recordAfter->{referencedFields};
5224+
}
52025225
/**
52035226
* Create a new instance of the intermediate model
52045227
*/
@@ -5207,44 +5230,42 @@ abstract class Model extends AbstractInjectionAware implements EntityInterface,
52075230
);
52085231

52095232
/**
5210-
* Has-one-through relations can only use one intermediate model.
52115233
* If it already exist, it can be updated with the new referenced key.
52125234
*/
5213-
if relation->getType() == Relation::HAS_ONE_THROUGH {
5214-
let existingIntermediateModel = intermediateModel->findFirst(
5215-
[
5216-
"[" . intermediateFields . "] = ?0",
5217-
"bind": [value]
5218-
]
5219-
);
5235+
let existingIntermediateModel = intermediateModel->findFirst(
5236+
[
5237+
intermediateConditions,
5238+
"bind": placeholders
5239+
]
5240+
);
52205241

5221-
if existingIntermediateModel {
5222-
let intermediateModel = existingIntermediateModel;
5242+
if existingIntermediateModel {
5243+
let intermediateModel = existingIntermediateModel;
5244+
}
5245+
if unlikely typeof columns === "array" {
5246+
for i in range(0, columnCount) {
5247+
let columnA = columns[i];
5248+
let columnB = intermediateFields[i];
5249+
let intermediateModel->{columnB} = this->{columnA};
52235250
}
5251+
} else {
5252+
/**
5253+
* Write value in the intermediate model
5254+
*/
5255+
let intermediateModel->{intermediateFields} = this->{columns};
5256+
}
5257+
if unlikely typeof referencedFields === "array" {
5258+
for i in range(0, referencedFieldsCount) {
5259+
let columnA = referencedFields[i];
5260+
let columnB = intermediateReferencedFields[i];
5261+
let intermediateModel->{columnB} = recordAfter->{columnA};
5262+
}
5263+
} else {
5264+
/**
5265+
* Write the intermediate value in the intermediate model
5266+
*/
5267+
let intermediateModel->{intermediateReferencedFields} = recordAfter->{referencedFields};
52245268
}
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-
);
52485269

52495270
/**
52505271
* Save the record and get messages
@@ -5264,27 +5285,56 @@ abstract class Model extends AbstractInjectionAware implements EntityInterface,
52645285
}
52655286
}
52665287
} 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) {
5288+
if unlikely typeof columns === "array" {
5289+
let columnCount = count(columns) - 1;
5290+
for recordAfter in relatedRecords {
5291+
for i in range(0, columnCount) {
5292+
let columnA = columns[i];
5293+
let columnB = referencedFields[i];
5294+
let recordAfter->{columnB} = this->{columnA};
5295+
}
52765296
/**
5277-
* Get the validation messages generated by the
5278-
* referenced model
5297+
* Save the record and get messages
52795298
*/
5280-
this->appendMessagesFrom(recordAfter);
5281-
5299+
if !recordAfter->doSave(visited) {
5300+
/**
5301+
* Get the validation messages generated by the
5302+
* referenced model
5303+
*/
5304+
this->appendMessagesFrom(recordAfter);
5305+
5306+
/**
5307+
* Rollback the implicit transaction
5308+
*/
5309+
connection->rollback(nesting);
5310+
5311+
return false;
5312+
}
5313+
}
5314+
} else {
5315+
for recordAfter in relatedRecords {
52825316
/**
5283-
* Rollback the implicit transaction
5317+
* Assign the value to the
52845318
*/
5285-
connection->rollback(nesting);
5319+
let recordAfter->{referencedFields} = this->{columns};
5320+
/**
5321+
* Save the record and get messages
5322+
*/
5323+
if !recordAfter->doSave(visited) {
52865324

5287-
return false;
5325+
/**
5326+
* Get the validation messages generated by the
5327+
* referenced model
5328+
*/
5329+
this->appendMessagesFrom(recordAfter);
5330+
5331+
/**
5332+
* Rollback the implicit transaction
5333+
*/
5334+
connection->rollback(nesting);
5335+
5336+
return false;
5337+
}
52885338
}
52895339
}
52905340
}

0 commit comments

Comments
 (0)