We have moved our forum to GitHub Discussions. For questions about Phalcon v3/v4/v5 you can visit here and for Phalcon v6 here.

Model implementation problems

Hi! I faced some problems with current Model implementation:

1) There is a bug with implicit transactions on saving model with not valid related models when using "exceptionOnFailedSave" enabled and nested transactions via savePoints:

$song = new Songs();
$album = new Albums();
$album->someRequiredProp = null;
$song->Album = $album;
$song->save();
// call $song->_preSaveRelatedRecords();
// begin implicit transaction
// call $album->save(); // throws an Exception because of empty someRequiredProp.
// no expected rollback() for implicit transaction here because exception is not handled in $song.
// this causes savepoint not exists exception next time rollback() is called.

2) I really don't like magic props and getters confusing behavior especially on saving related models:

$song = new Songs();
$album = new $album;
$album->title = 'foo';
$song->Album = $album;
$song->save();

$album = $song->getAlbum();
$album->title = 'bar';
$album->save();
echo $song->getAlbum()->title; // 'bar'

$song->save();
echo $song->getAlbum()->title; // 'foo'
// wtf?! why saving old related $album object with old title again when it's not actual anymore? we expect only $song object to be saved, because $album is not dirty anymore. We should keep only dirty related objects.

3) I prefer to use getters/setters approach only and would like to see magic setter for related objects.

Here is base class solving all those problems for me:

abstract class Model extends \Phalcon\MVC\Model
{
    public function save($data = null, $whiteList = null)
    {
        /**
         * Override save() method to handle
         * possible relations recursion and
         * cleanup related models bag after save.
         */
        $related = $this->_related;
        try {
            $result = parent::save($data, $whiteList);
            if (!$result) {
                $this->_related = $related;
            }
            return $result;
        } catch (\Exception $e) {
            $this->_related = $related;
            throw $e;
        }
    }

    protected function _preSaveRelatedRecords($conn, $related)
    {
        /**
         * Fix related models recursion problem.
         * Fix savepoint exception when using nested transactions
         * with model exceptionOnFailedSave.
         */
        $this->_related = null;
        try {
            $txLevel = $conn->getTransactionLevel();
            return parent::_preSaveRelatedRecords($conn, $related);
        } catch (\Exception $e) {
            while ($conn->getTransactionLevel() > $txLevel) {
                try {
                    $conn->rollback();
                } catch (\PDOException $pdoEx) {}
            }
            throw $e;
        }
    }

    public function __call($method, $args = null)
    {
        /**
         * Add magic setter for related models.
         */
        if (1 === count($args)
            && ($args[0] instanceof \Phalcon\MVC\ModelInterface || is_array($args[0]))
            && (0 === stripos($method, 'set'))
        ) {
            $related = substr($method, 3);
            $this->$related = $args[0];
            return $this;
        }
        return parent::__call($method, $args);
    }
}

Or am I just misunderstanding some basics and this is how it's designed? :-)

edited Jul '14

Forgot to mention one more issue with relation recursion:

$song = new Songs();
$album = new Albums();
$song->Album = $album;
$album->BestSong = $song;
$song->save(); // will cause infinit recursion and max depth limit error.

This is also resolved in my base Model class implementation by cleaning related models bag on first step. I beleive this is huge issue to be fixed too.

Hi there I have the same problem with infinit recursion I could fix the problem with your script momently Its a bug??? I have PHP 5.6.22 and Phalcon 3.0.0