phpcakephpcontainablecakephp-2.4

cakephp dynamic virtualfield or apply beforeFind callback with containable


I'm currently attaching a virtual field in beforeFind() because I need to insert the current user's id into the query. Calling find on a related model and containing this model includes statically defined virtual fields but not the one in beforeFind().. Calling find directly on the model includes the dynamically attached virtual field.

Here is my beforeFind callback:

public function beforeFind($query = array()) {
    $user_id = $this->getCurrentUser()['id'];
    $this->virtualFields = array_merge($this->virtualFields, array( 
        'cost_for_user' => sprintf('CASE WHEN Inventory.user_id = %s THEN Inventory.cost ELSE Inventory.cost_for_team END', $user_id),
    ));
    return $query;
}

Since cost_for_user is dynamically attached in beforeFind I can't copy the virtual fields over at runtime with like suggested in the cookbook. Is there a better callback for dynamically attaching virtual fields so there are included in contain results?


Solution

  • If it's just about the inclusion of virtual fields in the find results, then you could for example use a separate method that initializes them, and call this method from within the currently queried models beforeFind() callback, something like:

    public function beforeFind($query = array())
    {
        if(!parent::beforeFind($query))
        {
            return false;
        }
    
        $this->RelatedModel->setupVirtualFields();
        return true;
    }
    
    public function setupVirtualFields()
    {
        $user_id = $this->getCurrentUser()['id'];
        $this->virtualFields = array_merge($this->virtualFields, array
        ( 
            'cost_for_user' => sprintf('CASE WHEN Inventory.user_id = %s THEN Inventory.cost ELSE Inventory.cost_for_team END', $user_id),
        ));
    }
    

    You could also make this a little more generic by applying it to all models and associations, for example in the AppModel::beforeFind() callback or using a behavior:

    public function beforeFind($query)
    {
        if(!parent::beforeFind($query))
        {
            return false;
        }
    
        $this->_setupVirtualFields($this);
        foreach(array_keys($this->getAssociated()) as $modelName)
        {
            $this->_setupVirtualFields($this->{$modelName});
        }
    
        return true;
    }
    
    protected function _setupVirtualFields(Model $model)
    {
        $method = 'setupVirtualFields';
        if(method_exists($model, $method) && is_callable(array($model, $method)))
        {
            $model->setupVirtualFields();
        }
    }