Love Fuel?    Donate

FuelPHP Forums

Ask your question about FuelPHP in the appropriate forum, or help others by answering their questions.
Calculated fields with Observer
  • I created an Observer to automatically update calculated fields, and I would like to know if this is the best way :


    class Observer_Calculated extends \Orm\Observer
    {
        protected $fields = array();
        protected $fcts = array();
       
        public function __construct($class){
            foreach($class::properties() as $name=>$field){
                if (!isset($field['calculated']))
                    continue;
               
                $opt = $field['calculated'];
                if (is_string($opt))
                    $opt = array('fct'=>$opt);
                elseif (!is_array($opt))
                    $opt = array();
                if (!(isset($opt['fct']) && is_string($opt['fct'])))
                    $opt['fct'] = 'calc_'.$name;
                if (!method_exists($class, $opt['fct']))
                    continue;
               
                $this->fields[$name] = $opt;
            }
        }
       
        public function before_save(Model $model){
            $this->calculate($model);
        }
       
        /**
         * Calculate the fields
         * @param Model $model
         */
        public function calculate(Model $model){
            $this->fcts = array();
            foreach($this->fields as $name=>$opt){
                if (!in_array($opt['fct'], $this->fcts))
                    $this->fcts[] = $opt['fct'];
            }
           
            foreach($this->fcts as $fct){
                $model->$fct();
            }
        }
    }


    And for the usage :


    class Model_Panier_Panier extends \Matnat\Model
    {
        ...

        protected static $_properties = array(
            'id',
            'created_at',
            'updated_at',
            'session_id' => array(
                'data_type'=>'varchar',
                'label'=>'ID de session',
            ),
            'adresse_cp'=>array(
            ),
            'article_nb'=>array(
                'calculated'=>'calcul',
            ),
            'article_prix'=>array(
                'calculated'=>'calcul',
            ),
            'port'=>array(
                'calculated'=>'calcul',
            ),
        );
       
        protected static $_has_many = array(
            'contenu'=>array(
                'key_to'=>'panier_id',
                'model_to'=>'Model_Panier_Contenu',
                'cascade_delete'=>true,
            ),
        );
       
        protected static $_observers = array(
            ...
            'Matnat\Observer_Calculated' => array(
                'events' => array('before_save')
            ),
            ...
        );
       
        ...
       
        public function calcul()
        {
            $nb = 0;
            $montant = 0;
           
            if (count($this->contenu)){
                $nb = count($this->contenu);
                foreach($this->contenu as $contenu){
                    $montant += $contenu->prix;
                }
            }
           
            $this->article_nb = $nb;
            $this->article_prix = $montant;
            $this->port = ...;
        }
    }
  • Your coding standard make me shiver, but to each their own... ;)

    It doesn't really matter how you do it, either with a direct method like you do, or using a notifier.

    Note that per model class, only one observer instance is created, which is reused for all model instances. So nothing in the constructor (or anything else static) may depend on the contents of an object.

    Other than that, you're free to do what you want.
  • Oops... sorry for the coding... I used another Observer as example but it seems I misunderstood the usage !

    So, when the Observer is instanciated for a class, in my example Model_Panier_Panier, it looks into the properties and retrieve each field $name with the parameter 'calculated', and an optional method to update this field (or it looks if a default method exists, named "calc_$name").
    It may be simplified but imagine upgrades who needs further options and such a definition.
    I don't see where I make "bad" usage of the class in the constructor... I only make usage of $class::properties() and method_exists($class, $opt['fct']) and the data stored in the Observer ($this->fields) is used at each event call of the instance (associated with the model).

    When the Observer is triggered, it executes once every function defined in the model to update the fields tagged "calculated".

    In an uncertain future, I would like to trigger only a few of the methods used to update calculated fields, like in the OpenERP ORM but I don't know how to implement it yet
    and I don't have such time. In the OpenERP model, we can tell a calculated field to be updated (by triggering the associated method) only when a set of fields in a set of models are changed, but the architecture of the models is very different here and I'm not sure I could ever do the same...

    Can you  tell me where I'm wrong ? I would like to implement it correctly.

    You may laugh but... what is a notifier ..? and where (book, ebook, etc.) can I learn to code so that people may not be shiver again ..?

    Sorry for my English too, it is difficult for me to be clear.
  • HarroHarro
    Accepted Answer
    No need to aplogise, everyone has his own style. ;) I made the statement with a smiley...

    I didn't say you did anything wrong, I just clarified how observers work.

    You can have a look in the typing observer, it uses the orm_notifier method instead of specific event methods. You can compare it a bit with the router method in a controller.

Howdy, Stranger!

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

In this Discussion