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

Related models, rollbacks and afterCreate events

Hi, I have tree related models users & courses & EmailConfirmations as follow:

Users -> hasManyToMant -> Courses
Users-> hasMany -> EmailConfirmations

In my Users model I have afterCreate method:

    /**
     * Send a confirmation e-mail to the user if the account is not active
     */
    public function afterCreate()
    {
        if ($this->isActive == self::NOT_ACTIVE) {

            $emailConfirmation = new EmailConfirmations();

            $emailConfirmation->userId = $this->id;

            if ($emailConfirmation->save()) {
                $this->getDI()
                    ->getFlashSession()
                    ->notice('A confirmation mail has been sent to ' . $this->email);
            } else {
                $this->getDI()
                    ->getFlashSession()
                    ->error($emailConfirmation->getMessages());
            }
        }
    }

and In EmailConfirmations, I have:

    /**
     * Send a confirmation e-mail to the user after save the user confirmation params
     */
    public function afterCreate()
    {
        $this->getDI()
            ->getMail()
            ->send(array(
                $this->user->email => $this->user->getFullName()
            ), "Please confirm your email", 'confirmation', array(
                'confirmUrl' => 'confirm/' . $this->code . '/' . $this->user->email
            ));
    }

In controller I'm trying to create a new user and assign a course to him:

$user = new Users();

$user->assign(array(
  'firstName' => $this->request->getPost('firstName', 'striptags'),
  'lastName' => $this->request->getPost('lastName', 'striptags'),
  'email' => $this->request->getPost('email'),
  'password' => $this->security->hash($this->request->getPost('password')),
  'registeredIP' => $this->request->getClientAddress()
));

$course = new Courses();
$user->teachingCourses = array($course); // <-- many to many alias

if ($user->save()) {
  return $this->dispatcher->forward(array(
  'controller' => 'index',
  'action' => 'index'
));
}

Problem is when there is an error in course insertion (such as missing model required fields and virtual foreignKey errors), all insertion rollback but afterCreate event in Users model & EmailConfirmations model calls and email sent to user without any record in confirmation table!

Why thoes events run if nothing inserted into tables, and what can I do to make this works?

Thanks

If I change afterCreate to afterSave everything work perfectly! I do not understand why!

Browsing the source I found that (https://github.com/phalcon/cphalcon/blob/master/phalcon/mvc/model.zep#L2776)

It seams the _postSave() calls before rollback, so if we have any rollback afterUpdate or afterCreate already calls anyway!

    /**
     * Executes internal events after save a record
     */
    protected function _postSave(boolean success, boolean exists) -> boolean
    {
        if success === true {
            if exists {
                this->fireEvent("afterUpdate");
            } else {
                this->fireEvent("afterCreate");
            }
        }

        return success;
    }

But afterSave calls after rollback success! (https://github.com/phalcon/cphalcon/blob/master/phalcon/mvc/model.zep#L2795) I think _postSave should be call after rollback and just before emitting afterSave event.

Bug o feature?

If an exception is thrown in one of the afterCreate events ( or anywhere before transaction gets committed), transaction rollbacks. So the email may be sent but the transaction gets rollback after that and rows gets deleted. I would add a status column to EmailConfirmations. Default value can be 'queued' and I would write a simple CLI task (of course with CliDi) to send queued emails. When an email gets sent, then I would change the status to sent.