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 events not firing after findFirst() but do fire after save()

I am attempting to create a function in a Base Model class that would allow me to register events that would fire afterFetch, beforeSave and afterSave so that I can convert unix timestamps to mysql timestamps in utc, and vice versa.

I want the code to go into the base model, but actually call the registration in the actual model, in case there are models that shouldn't operate this way.

I have for a base model.

<?php
use Phalcon\Db\Column;
use Phalcon\Events\Manager as EventsManager;

class ModelBase extends Phalcon\Mvc\Model {

    /**
     * this attaches listeners to the beforeSave, afterSave, and afterFetch events of
     * the given model to convert the dates/datetimes to/from unix timestamps and mysql dattimes in utc
     */
    public function register_utc_conversion() {
        $eventsManager = new EventsManager();
        // Attach an anonymous function as a listener for "model" events
        $eventsManager->attach('model', function ($event, $model) {
        if ($event->getType() == 'afterFetch' || $event->getType() == 'afterSave') {
                $fields = $model->getModelsMetaData()->getDataTypes($model);
                foreach ($fields as $field => $type) {
                    if (($type == Column::TYPE_DATE || $type == Column::TYPE_DATETIME) && $model->$field !== null) {
                        $model->$field = $model->getDI()->get('datehelper')->from_utc($model->$field);
                    }
                }
        } else if ($event->getType() == 'beforeSave') {
                $fields = $model->getModelsMetaData()->getDataTypes($model);
                foreach ($fields as $field => $type) {
                    if ($type == Column::TYPE_DATE && $model->$field !== null) {
                        $model->$field = $model->getDI()->get('datehelper')->ts_utc($model->$field, 'Y-m-d');
                    } else if ($type == Column::TYPE_DATETIME && $model->$field !== null) {
                        $model->$field = $model->getDI()->get('datehelper')->ts_utc($model->$field, 'Y-m-d H:i:s');
                    }
                }
        }
        return true;
        });
        $this->setEventsManager($eventsManager);
    }
}

and for an actual model: (Bellsell_Model extends ModelBase)

<?php
class Bellsell_Invoices extends Bellsell_Model {

    public $id;
    public $invoice_number;
    public $customers_id;
    public $notes;
    public $arrive_date;
    public $pickup_date;
    public $ordered_from;
    public $include_sales_tax;
    public $po_number;
    public $created;
    public $updated;
    public $invoiced;

    public function initialize() {
        parent::initialize();
        $this->setSource('invoices');
        $this->hasMany('id', 'Bellsell_Items', 'invoices_id');
        $this->belongsTo('customers_id', 'Bellsell_Customers', 'id');
        $this->register_utc_conversion();
    }
}

if I load the model using findFirst, the eventsmanager doesn't seem to fire any events, however if I then save the model, the events appear to be fired. Putting code in the afterFetch() function of the model appears to work, but that defeats what I am trying to accomplish by using EventsManager and being able to register it on a per model basis. What would be the difference in the afterFetch() function working vs the EventsMananger not working. Quite certain I am missing something obvious. Using 2.0.8.

Are you sure findFirst return full model ? AfterFetch will fire only if you are selecting full object, if you select some columns then it wont work. Same if you select it fo example with modelsManager.

I am using for testing

$invoice = Bellsell_Invoices::findFirst(873);

if I look at $invoice->arrive_date it hasn't been modified after the fetch, but if I do $invoice->save() then look at $invoice->arrive_date, it has been modified.

I was seeing some posts that allude to the fact that findFirst may not fire events until after the object is accessed, but as I mentioned earlier, if I put code in the afterFetch() function of Bellsell_Invoices, that fires, but for some reason the event in EventManager doesn't fire. I'm tempted to upgrade to 2.1 to see if there is any difference, but not sure I want to risk breaking other things just yet.

Started poking around in the sourcecode and found that the afterFetch event isn't fired in the Events Manager, but the afterFetch() function of the model is called if it exists. I made the appropriate changes in model.zep and recompiled and so far so good. My registered events seem to fire. Will do some more testing then see about doing a pull request with the changes (once I figure out how to do a pull request :)).