Skip to content

Commit 8795a72

Browse files
committed
Fix #16029, added ability to save Multiple fields
1 parent 450009c commit 8795a72

24 files changed

+2037
-150
lines changed

CHANGELOG-5.0.md

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
# Changelog
22

3-
## [5.4.0](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+
- Model relations now can have multiple fields[#16029](https://github.com/phalcon/cphalcon/issues/16029)
47

58
### Fixed
69
- Model Annotation strategy did not work with empty_string [#16426] (https://github.com/phalcon/cphalcon/issues/16426)

phalcon/Mvc/Model.zep

+140-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, h;
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,38 @@ 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+
let 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 relation->getType() === Relation::HAS_MANY_THROUGH {
5189+
if unlikely typeof referencedFields === "array" {
5190+
let referencedFieldsCount = count(referencedFields) - 1;
5191+
for j in range(0, referencedFieldsCount) {
5192+
let t = j + i;
5193+
let conditions[] = "[". intermediateReferencedFields[j] . "] = :APR" . t . ":";
5194+
}
5195+
} else {
5196+
let conditions[] = "[". intermediateReferencedFields . "] = :APR" . i . ":";
5197+
}
5198+
}
5199+
let intermediateConditions = join(" AND ", conditions);
51845200
for recordAfter in relatedRecords {
51855201
/**
51865202
* Save the record and get messages
@@ -5191,14 +5207,25 @@ abstract class Model extends AbstractInjectionAware implements EntityInterface,
51915207
* referenced model
51925208
*/
51935209
this->appendMessagesFrom(recordAfter);
5194-
5210+
51955211
/**
51965212
* Rollback the implicit transaction
51975213
*/
51985214
connection->rollback(nesting);
5199-
5215+
52005216
return false;
52015217
}
5218+
if relation->getType() === Relation::HAS_MANY_THROUGH {
5219+
if unlikely typeof referencedFields === "array" {
5220+
for j in range(0, referencedFieldsCount) {
5221+
let columnA = referencedFields[j];
5222+
let t = j + i;
5223+
let placeholders["APR" . t] = recordAfter->{columnA};
5224+
}
5225+
} else {
5226+
let placeholders["APR" . i] = recordAfter->{referencedFields};
5227+
}
5228+
}
52025229
/**
52035230
* Create a new instance of the intermediate model
52045231
*/
@@ -5207,45 +5234,43 @@ abstract class Model extends AbstractInjectionAware implements EntityInterface,
52075234
);
52085235

52095236
/**
5210-
* Has-one-through relations can only use one intermediate model.
52115237
* If it already exist, it can be updated with the new referenced key.
52125238
*/
5213-
if relation->getType() == Relation::HAS_ONE_THROUGH {
5214-
let existingIntermediateModel = intermediateModel->findFirst(
5215-
[
5216-
"[" . intermediateFields . "] = ?0",
5217-
"bind": [value]
5218-
]
5219-
);
5239+
let existingIntermediateModel = intermediateModel->findFirst(
5240+
[
5241+
intermediateConditions,
5242+
"bind": placeholders
5243+
]
5244+
);
52205245

5221-
if existingIntermediateModel {
5222-
let intermediateModel = existingIntermediateModel;
5246+
if existingIntermediateModel {
5247+
let intermediateModel = existingIntermediateModel;
5248+
}
5249+
if !existingIntermediateModel || relation->getType() === Relation::HAS_ONE_THROUGH {
5250+
/**
5251+
* Write value in the intermediate model
5252+
*/
5253+
if unlikely typeof columns === "array" {
5254+
for h in range(0, columnCount) {
5255+
let columnA = columns[h];
5256+
let columnB = intermediateFields[h];
5257+
let intermediateModel->{columnB} = this->{columnA};
5258+
}
5259+
} else {
5260+
let intermediateModel->{intermediateFields} = this->{columns};
5261+
}
5262+
if unlikely typeof referencedFields === "array" {
5263+
let referencedFieldsCount = count(referencedFields) - 1;
5264+
for h in range(0, referencedFieldsCount) {
5265+
let columnA = referencedFields[h];
5266+
let columnB = intermediateReferencedFields[h];
5267+
let intermediateModel->{columnB} = recordAfter->{columnA};
5268+
}
5269+
} else {
5270+
let intermediateModel->{intermediateReferencedFields} = recordAfter->{referencedFields};
52235271
}
52245272
}
52255273

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-
);
5248-
52495274
/**
52505275
* Save the record and get messages
52515276
*/
@@ -5264,27 +5289,56 @@ abstract class Model extends AbstractInjectionAware implements EntityInterface,
52645289
}
52655290
}
52665291
} 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) {
5292+
if unlikely typeof columns === "array" {
5293+
let columnCount = count(columns) - 1;
5294+
for recordAfter in relatedRecords {
5295+
for i in range(0, columnCount) {
5296+
let columnA = columns[i];
5297+
let columnB = referencedFields[i];
5298+
let recordAfter->{columnB} = this->{columnA};
5299+
}
52765300
/**
5277-
* Get the validation messages generated by the
5278-
* referenced model
5301+
* Save the record and get messages
52795302
*/
5280-
this->appendMessagesFrom(recordAfter);
5281-
5303+
if !recordAfter->doSave(visited) {
5304+
/**
5305+
* Get the validation messages generated by the
5306+
* referenced model
5307+
*/
5308+
this->appendMessagesFrom(recordAfter);
5309+
5310+
/**
5311+
* Rollback the implicit transaction
5312+
*/
5313+
connection->rollback(nesting);
5314+
5315+
return false;
5316+
}
5317+
}
5318+
} else {
5319+
for recordAfter in relatedRecords {
52825320
/**
5283-
* Rollback the implicit transaction
5321+
* Assign the value to the
52845322
*/
5285-
connection->rollback(nesting);
5323+
let recordAfter->{referencedFields} = this->{columns};
5324+
/**
5325+
* Save the record and get messages
5326+
*/
5327+
if !recordAfter->doSave(visited) {
52865328

5287-
return false;
5329+
/**
5330+
* Get the validation messages generated by the
5331+
* referenced model
5332+
*/
5333+
this->appendMessagesFrom(recordAfter);
5334+
5335+
/**
5336+
* Rollback the implicit transaction
5337+
*/
5338+
connection->rollback(nesting);
5339+
5340+
return false;
5341+
}
52885342
}
52895343
}
52905344
}

0 commit comments

Comments
 (0)