Love Fuel?    Donate

FuelPHP Forums

Ask your question about FuelPHP in the appropriate forum, or help others by answering their questions.
Using Observers to add custom properties to Orm Model to modify the results of ::find()
  • I am having trouble understanding how to use observers with ORM models correctly. I think my question is similar to this one: http://fuelphp.com/forums/discussion/11500/custom-model-properties-extension

    I have an ORM Model, and use the following code to grab a list of pages, and later i pass the result to my view.

    $data['pages'] = Model_pages::find('all');

    In my view i cycle through the results in this way:

    foreach($pages as $page) {
    ....
    echo $page->title;
    ....
    echo \Html::anchor(
    \Router::get('content_pages_edit',array('page_id'=>$page->page_id)),
    'Edit Page' )
    ....
    }

    The line of code for echoing the 'edit' link for each page i would like to move into the model.

    What i'm trying to do is use observers to change the result of Model_pages::find() and add custom properties to each object in the results, after this it is then passed to $data['pages'] as usual.

    In my model i would like a method which would receive the results of ::find(), and allow me to manipulate the values, before returning the modified results, something like this:

    ....
    protected static $_observers = array('Orm\\Observer_Self');
    ....
    protected static function _event_im_not_sure_of( $results ) {
    ....
    foreach($results as $index=>$result)
    $results[ $index ]->link_edit_page = \Router::get(
    'content_pages_edit',
    array('page_id'=>$result->page_id)
    );
    ....
    return $results;
    }

    And hopefully, in my view i can echo the edit link like this:

    foreach($pages as $page) {
    ....
    echo $page->title;
    ....
    echo \Html::anchor( $page->link_edit_page, 'Edit Page' );
    ....
    }

    If possible it would be good to use a 'self' observer, so i can keep the code inside the model. Is there a way to call a method on the model each time ::find() is called?

    I'm not sure of which 'event' im supposed to use, i have tried a few with no luck.

  • If you want to modify the data in the model object after it's been read, you need the 'after_load' event.

    There are several things wrong with your approach:

    When using Observer_Self, it calls methods inside the model itself, which must have the name of the event. So in case of 'after_load', you method should be defined as
    protected function _event_after_load($model) {}
    An observer is fired per model object, so if your find returns 10 records, 10 model objects are created, and the observer is fired on each of them. It gets passed the model object itself.

    This means your observer code must be something like:
    $model->link_edit_page = \Router::get(
    'content_pages_edit',
    array('page_id'=>$model->page_id)
    );
    There is no need to return anything, you modify the passed model object directly.
  • Thanks for the feedback Harro, i did try messing with the _event_after_load method previously with no luck, i have implemented your suggestions and get the following error:

    "BadMethodCallException [ Error ]: Call to undefined method Content_pages\Model_Pages::_event_after_load()"

    <?php 

    namespace Content_pages;

    class Model_Pages extends \Orm\Model {

    protected static $_observers = array('Orm\\Observer_Self');

    protected static $_table_name = 'pages';

    protected static $_primary_key = array('page_id');

    protected static $_properties = array(
    'page_id',
    'title' => array(
    'data_type' => 'varchar',
    'label' => 'Title',
    'validation' => array('required', 'min_length' => array(3), 'max_length' => array(255)),
    'form' => array('type' => 'text'),
    'default' => ''
    ),
    'body' => array(
    'data_type' => 'longtext',
    'label' => 'Body',
    'validation' => array('required', 'min_length' => array(3), 'max_length' => array(4000)),
    'form' => array('type' => 'textarea'),
    'default' => ''
    )

    );

    protected function _event_after_load($model) {

    $model->link_edit_page = \Router::get(
    'content_pages_edit',
    array('page_id'=>$model->page_id)
    );

    }

    }

    I have also tried declaring the event function as a protected static function too, and get the same error.

  • If i remove the code for the function, it stops complaining that it can't find the method i just removed, if i re-add the function, it says it can't find it lol, how strange.
  • Ah, if i change the function from protected to public i get a new error:

    "Fuel\Core\PhpErrorException [ Warning ]:
    Missing argument 1 for Content_pages\Model_Pages::_event_after_load()"

    This is what i was having trouble with before, i couldn't find a way to get the model result inside the after_load event, looking through Fuel's observers i noticed some events had the model being passed in like this: public function before_save(Model $obj) {}
  • Harro VertonHarro Verton
    Accepted Answer
    It's called from the observer class, so it has to be public.

    And I checked Observer_Self, and it doesn't pass the model object to the event method, like all other observers. Understandable, since you can access the model instance using $this, but not very standard and confusing.

    So you need to remove the argument, and change $model to $this.

    Sorry for giving the incorrect advice.
  • Thanks Harro! That seems to have worked ok :) You've been a great help again.

    For any google-ers, this is working the code for the model:

    <?php 

    namespace Content_pages;

    class Model_Pages extends \Orm\Model {

    protected static $_observers = array('Orm\\Observer_Self');

    protected static $_table_name = 'pages';

    protected static $_primary_key = array('page_id');

    protected static $_properties = array(
    'page_id', // both validation & typing observers will ignore the PK
    'title' => array(
    'data_type' => 'varchar',
    'label' => 'Title',
    'validation' => array('required', 'min_length' => array(3), 'max_length' => array(255)),
    'form' => array('type' => 'text'),
    'default' => ''
    ),
    'body' => array(
    'data_type' => 'longtext',
    'label' => 'Body',
    'validation' => array('required', 'min_length' => array(3), 'max_length' => array(4000)),
    'form' => array('type' => 'textarea'),
    'default' => ''
    )

    );

    public function _event_after_load() {

    $this->link_edit = \Router::get(
    'content_pages_edit',
    array('page_id'=>$this->page_id)
    );

    }

    }

Howdy, Stranger!

It looks like you're new here. If you want to get involved, click one of these buttons!

In this Discussion