@@ -41,6 +41,8 @@ use Phalcon\Mvc\Model\TransactionInterface;
4141use Phalcon\Mvc\Model\ValidationFailed;
4242use Phalcon\Mvc\ModelInterface;
4343use Phalcon\Filter\Validation\ValidationInterface;
44+ use Phalcon\Support\Collection;
45+ use Phalcon\Support\Collection\CollectionInterface;
4446use Serializable;
4547
4648/**
@@ -2579,12 +2581,35 @@ abstract class Model extends AbstractInjectionAware implements EntityInterface,
25792581 * $robot->save();
25802582 *```
25812583 */
2584+
25822585 public function save () -> bool
2586+ {
2587+ var visited;
2588+ let visited = new Collection ();
2589+ return this -> doSave(visited);
2590+ }
2591+
2592+ /**
2593+ * Inserted or updates model instance, expects a visited list of objects.
2594+ *
2595+ * @param CollectionInterface $visited
2596+ *
2597+ * @return bool
2598+ */
2599+ public function doSave (<CollectionInterface> visited ) -> bool
25832600 {
25842601 var metaData, schema, writeConnection, readConnection, source, table,
2585- identityField, exists, success, relatedToSave;
2602+ identityField, exists, success, relatedToSave, objId ;
25862603 bool hasRelatedToSave;
25872604
2605+ let objId = spl_object_id(this );
2606+
2607+ if true === visited-> has(objId) {
2608+ return true ;
2609+ }
2610+
2611+ visited-> set (objId, this );
2612+
25882613 let metaData = this -> getModelsMetaData();
25892614
25902615 /**
@@ -2610,7 +2635,7 @@ abstract class Model extends AbstractInjectionAware implements EntityInterface,
26102635 let hasRelatedToSave = count(relatedToSave) > 0 ;
26112636
26122637 if hasRelatedToSave {
2613- if this -> preSaveRelatedRecords(writeConnection, relatedToSave) === false {
2638+ if this -> preSaveRelatedRecords(writeConnection, relatedToSave, visited ) === false {
26142639 return false ;
26152640 }
26162641 }
@@ -2695,7 +2720,7 @@ abstract class Model extends AbstractInjectionAware implements EntityInterface,
26952720 /**
26962721 * Change the dirty state to persistent
26972722 */
2698- if success {
2723+ if true === success {
26992724 let this -> dirtyState = self :: DIRTY_STATE_PERSISTENT ;
27002725 }
27012726
@@ -2711,7 +2736,8 @@ abstract class Model extends AbstractInjectionAware implements EntityInterface,
27112736 */
27122737 let success = this -> postSaveRelatedRecords(
27132738 writeConnection,
2714- relatedToSave
2739+ relatedToSave,
2740+ visited
27152741 );
27162742 }
27172743 }
@@ -4964,9 +4990,11 @@ abstract class Model extends AbstractInjectionAware implements EntityInterface,
49644990 * Saves related records that must be stored prior to save the master record
49654991 *
49664992 * @param ModelInterface[] related
4993+ * @param CollectionInterface visited
49674994 * @return bool
49684995 */
4969- protected function preSaveRelatedRecords (<AdapterInterface> connection, related ) -> bool
4996+
4997+ protected function preSaveRelatedRecords (<AdapterInterface> connection, related, <CollectionInterface> visited ) -> bool
49704998 {
49714999 var className, manager, type, relation, columns, referencedFields, nesting, name, record;
49725000
@@ -5006,7 +5034,6 @@ abstract class Model extends AbstractInjectionAware implements EntityInterface,
50065034 " Only objects can be stored as part of belongs-to relations in '" . get_class(this ) . " ' Relation " . name
50075035 );
50085036 }
5009-
50105037 let columns = relation-> getFields(),
50115038 referencedFields = relation-> getReferencedFields();
50125039// let columns = relation->getFields(),
@@ -5023,7 +5050,7 @@ abstract class Model extends AbstractInjectionAware implements EntityInterface,
50235050 * If dynamic update is enabled, saving the record must not take any action
50245051 * Only save if the model is dirty to prevent circular relations causing an infinite loop
50255052 */
5026- if record-> dirtyState !== Model:: DIRTY_STATE_PERSISTENT && ! record-> save( ) {
5053+ if record-> dirtyState !== Model:: DIRTY_STATE_PERSISTENT && ! record-> doSave(visited ) {
50275054 /**
50285055 * Get the validation messages generated by the
50295056 * referenced model
@@ -5072,9 +5099,10 @@ abstract class Model extends AbstractInjectionAware implements EntityInterface,
50725099 * Save the related records assigned in the has-one/has-many relations
50735100 *
50745101 * @param ModelInterface[] related
5102+ * @param CollectionInterface visited
50755103 * @return bool
50765104 */
5077- protected function postSaveRelatedRecords (<AdapterInterface> connection, related ) -> bool
5105+ protected function postSaveRelatedRecords (<AdapterInterface> connection, related, <CollectionInterface> visited ) -> bool
50785106 {
50795107 var nesting, className, manager, relation, name, record,
50805108 columns, referencedModel, referencedFields, relatedRecords, value,
@@ -5157,7 +5185,7 @@ abstract class Model extends AbstractInjectionAware implements EntityInterface,
51575185 /**
51585186 * Save the record and get messages
51595187 */
5160- if ! recordAfter-> save( ) {
5188+ if ! recordAfter-> doSave(visited ) {
51615189 /**
51625190 * Get the validation messages generated by the
51635191 * referenced model
@@ -5221,7 +5249,7 @@ abstract class Model extends AbstractInjectionAware implements EntityInterface,
52215249 /**
52225250 * Save the record and get messages
52235251 */
5224- if ! intermediateModel-> save( ) {
5252+ if ! intermediateModel-> doSave(visited ) {
52255253 /**
52265254 * Get the validation messages generated by the referenced model
52275255 */
@@ -5244,7 +5272,7 @@ abstract class Model extends AbstractInjectionAware implements EntityInterface,
52445272 /**
52455273 * Save the record and get messages
52465274 */
5247- if ! recordAfter-> save( ) {
5275+ if ! recordAfter-> doSave(visited ) {
52485276 /**
52495277 * Get the validation messages generated by the
52505278 * referenced model
0 commit comments