Skip to content

Commit

Permalink
Fix #16029, added ability to save Multiple fields
Browse files Browse the repository at this point in the history
  • Loading branch information
rudiservo committed Sep 7, 2023
1 parent d5a12bf commit 8da39a8
Show file tree
Hide file tree
Showing 20 changed files with 1,311 additions and 150 deletions.
7 changes: 6 additions & 1 deletion CHANGELOG-5.0.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
# Changelog

## [5.3.1](https://github.com/phalcon/cphalcon/releases/tag/v5.3.1) (xxxx-xx-xx)
## [5.4.0](https://github.com/phalcon/cphalcon/releases/tag/v5.4.0) (xxxx-xx-xx)

### Added

- 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)

### Fixed

- Infinite save loop in Model::save() [#16395](https://github.com/phalcon/cphalcon/issues/16395)


Expand Down
222 changes: 136 additions & 86 deletions phalcon/Mvc/Model.zep
Original file line number Diff line number Diff line change
Expand Up @@ -4996,8 +4996,8 @@ abstract class Model extends AbstractInjectionAware implements EntityInterface,

protected function preSaveRelatedRecords(<AdapterInterface> connection, related, <CollectionInterface> visited) -> bool
{
var className, manager, type, relation, columns, referencedFields, nesting, name, record;

var className, manager, type, relation, columns, referencedFields, nesting, name, record, columnA, columnB;
int columnCount, i;
let nesting = false;

/**
Expand Down Expand Up @@ -5034,17 +5034,6 @@ abstract class Model extends AbstractInjectionAware implements EntityInterface,
"Only objects can be stored as part of belongs-to relations in '" . get_class(this) . "' Relation " . name
);
}
let columns = relation->getFields(),
referencedFields = relation->getReferencedFields();
// let columns = relation->getFields(),
// referencedModel = relation->getReferencedModel(),
// referencedFields = relation->getReferencedFields();

if unlikely typeof columns === "array" {
connection->rollback(nesting);

throw new Exception("Not implemented in '" . get_class(this) . "' Relation " . name);
}

/**
* If dynamic update is enabled, saving the record must not take any action
Expand All @@ -5069,7 +5058,18 @@ abstract class Model extends AbstractInjectionAware implements EntityInterface,
* Read the attribute from the referenced model and assign
* it to the current model
*/
let this->{columns} = record->readAttribute(referencedFields);
let columns = relation->getFields(),
referencedFields = relation->getReferencedFields();
if unlikely typeof columns === "array" {
let columnCount = count(columns) - 1;
for i in range(0, columnCount) {
let columnA = columns[i];
let columnB = referencedFields[i];
let this->{columnA} = record->{columnB};
}
} else {
let this->{columns} = record->{referencedFields};
}
}
}
}
Expand Down Expand Up @@ -5105,11 +5105,14 @@ abstract class Model extends AbstractInjectionAware implements EntityInterface,
protected function postSaveRelatedRecords(<AdapterInterface> connection, related, <CollectionInterface> visited) -> bool
{
var nesting, className, manager, relation, name, record,
columns, referencedModel, referencedFields, relatedRecords, value,
columns, referencedModel, referencedFields, relatedRecords,
recordAfter, intermediateModel, intermediateFields,
intermediateValue, intermediateModelName,
intermediateReferencedFields, existingIntermediateModel;
intermediateModelName,
intermediateReferencedFields, existingIntermediateModel, columnA, columnB;
bool isThrough;
int columnCount, referencedFieldsCount, i, j, t;
string intermediateConditions;
array conditions, placeholders;

let nesting = false,
className = get_class(this),
Expand Down Expand Up @@ -5144,12 +5147,6 @@ abstract class Model extends AbstractInjectionAware implements EntityInterface,
referencedModel = relation->getReferencedModel(),
referencedFields = relation->getReferencedFields();

if unlikely typeof columns === "array" {
connection->rollback(nesting);

throw new Exception("Not implemented in '" . className . "' on Relation " . name);
}

/**
* Create an implicit array for has-many/has-one records
*/
Expand All @@ -5159,18 +5156,6 @@ abstract class Model extends AbstractInjectionAware implements EntityInterface,
let relatedRecords = record;
}

if unlikely !fetch value, this->{columns} {
connection->rollback(nesting);

throw new Exception(
"The column '" . columns . "' needs to be present in the model '" . className . "'"
);
}

/**
* Get the value of the field from the current model
* Check if the relation is a has-many-to-many
*/
let isThrough = (bool) relation->isThrough();

/**
Expand All @@ -5180,7 +5165,36 @@ abstract class Model extends AbstractInjectionAware implements EntityInterface,
let intermediateModelName = relation->getIntermediateModel(),
intermediateFields = relation->getIntermediateFields(),
intermediateReferencedFields = relation->getIntermediateReferencedFields();
let placeholders = [];
let conditions = [];

/**
* Always check for existing intermediate models
* otherwise conflicts will arise on insert instead of update
*/
if unlikely typeof columns === "array" {
let columnCount = count(columns) - 1;
for i in range(0, columnCount) {
let columnA = columns[i];
let conditions[] = "[". intermediateFields[i] . "] = :APR" . i . ":",
placeholders["APR" . i] = this->{columnA};
}
let i = columnCount + 1;
} else {
let conditions[] = "[" . intermediateFields . "] = :APR0:";
let placeholders["APR0"] = this->{columns};
let i = 1;
}
if unlikely typeof referencedFields === "array" {
let referencedFieldsCount = count(referencedFields) - 1;
for j in range(0, referencedFieldsCount) {
let t = j + i;
let conditions[] = "[". intermediateReferencedFields[j] . "] = :APR" . t . ":";
}
} else {
let conditions[] = "[". intermediateReferencedFields . "] = :APR" . i . ":";
}
let intermediateConditions = join(" AND ", conditions);
for recordAfter in relatedRecords {
/**
* Save the record and get messages
Expand All @@ -5191,14 +5205,23 @@ abstract class Model extends AbstractInjectionAware implements EntityInterface,
* referenced model
*/
this->appendMessagesFrom(recordAfter);

/**
* Rollback the implicit transaction
*/
connection->rollback(nesting);

return false;
}
if unlikely typeof referencedFields === "array" {
for j in range(0, referencedFieldsCount) {
let columnA = referencedFields[j];
let t = j + i;
let placeholders["APR" . t] = recordAfter->{columnA};
}
} else {
let placeholders["APR" . i] = recordAfter->{referencedFields};
}
/**
* Create a new instance of the intermediate model
*/
Expand All @@ -5207,44 +5230,42 @@ abstract class Model extends AbstractInjectionAware implements EntityInterface,
);

/**
* Has-one-through relations can only use one intermediate model.
* If it already exist, it can be updated with the new referenced key.
*/
if relation->getType() == Relation::HAS_ONE_THROUGH {
let existingIntermediateModel = intermediateModel->findFirst(
[
"[" . intermediateFields . "] = ?0",
"bind": [value]
]
);
let existingIntermediateModel = intermediateModel->findFirst(
[
intermediateConditions,
"bind": placeholders
]
);

if existingIntermediateModel {
let intermediateModel = existingIntermediateModel;
if existingIntermediateModel {
let intermediateModel = existingIntermediateModel;
}
if unlikely typeof columns === "array" {
for i in range(0, columnCount) {
let columnA = columns[i];
let columnB = intermediateFields[i];
let intermediateModel->{columnB} = this->{columnA};
}
} else {
/**
* Write value in the intermediate model
*/
let intermediateModel->{intermediateFields} = this->{columns};
}
if unlikely typeof referencedFields === "array" {
for i in range(0, referencedFieldsCount) {
let columnA = referencedFields[i];
let columnB = intermediateReferencedFields[i];
let intermediateModel->{columnB} = recordAfter->{columnA};
}
} else {
/**
* Write the intermediate value in the intermediate model
*/
let intermediateModel->{intermediateReferencedFields} = recordAfter->{referencedFields};
}

/**
* Write value in the intermediate model
*/
intermediateModel->writeAttribute(
intermediateFields,
value
);

/**
* Get the value from the referenced model
*/
let intermediateValue = recordAfter->readAttribute(
referencedFields
);

/**
* Write the intermediate value in the intermediate model
*/
intermediateModel->writeAttribute(
intermediateReferencedFields,
intermediateValue
);

/**
* Save the record and get messages
Expand All @@ -5264,27 +5285,56 @@ abstract class Model extends AbstractInjectionAware implements EntityInterface,
}
}
} else {
for recordAfter in relatedRecords {
/**
* Assign the value to the
*/
recordAfter->writeAttribute(referencedFields, value);
/**
* Save the record and get messages
*/
if !recordAfter->doSave(visited) {
if unlikely typeof columns === "array" {
let columnCount = count(columns) - 1;
for recordAfter in relatedRecords {
for i in range(0, columnCount) {
let columnA = columns[i];
let columnB = referencedFields[i];
let recordAfter->{columnB} = this->{columnA};
}
/**
* Get the validation messages generated by the
* referenced model
* Save the record and get messages
*/
this->appendMessagesFrom(recordAfter);

if !recordAfter->doSave(visited) {
/**
* Get the validation messages generated by the
* referenced model
*/
this->appendMessagesFrom(recordAfter);

/**
* Rollback the implicit transaction
*/
connection->rollback(nesting);

return false;
}
}
} else {
for recordAfter in relatedRecords {
/**
* Rollback the implicit transaction
* Assign the value to the
*/
connection->rollback(nesting);
let recordAfter->{referencedFields} = this->{columns};
/**
* Save the record and get messages
*/
if !recordAfter->doSave(visited) {

return false;
/**
* Get the validation messages generated by the
* referenced model
*/
this->appendMessagesFrom(recordAfter);

/**
* Rollback the implicit transaction
*/
connection->rollback(nesting);

return false;
}
}
}
}
Expand Down
Loading

0 comments on commit 8da39a8

Please sign in to comment.