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

callback function after fetch related model

When a model has a relationship with another model $model->hasMany(...), how can I call a function to perform some operation on fetched results ? Example:

public function initlialize()
{
            $this->hasMany('primaryKey', RelatedModel::class, 'refPrimaryKey', [
                'alias' => 'somealias',
                'callback' => 'some_method_in_related_model'
            ]);
}

How to obtain such functionality ?



8.4k
edited Apr '19

there is no option for a callback but you can use Resultset::filter()

$company
//or
$robot = Robots::findFirst();
$parts = $robot->parts; // get relation

// apply callback on each result
$filtered = $parts->filter(
    function ($part) {
        if ($part->id < 3) {
            // you can return an object or an array otherwise will be discarded
            return $part;
        }
    }
);

$filtered is an array with returned values from the callback

you can apply conditions on the qurey for all relation calls like:

$this->hasMany(
    'id', 
    'Invoices', 
    'inv_id', 
    [
        'alias'    => 'InvoicesUnpaid',
        'params'   => [
            'conditions' => "inv_status <> :status:",
            'bind' => ['status' => 'unpaid']
        ]
    ]
);

or you can apply conditions on specific call like:

$unpaidInvoices = $company->getRelated(
    'Invoices', 
    [
        'conditions' => "inv_status = 'paid'",
        'order'      => 'inv_created_date ASC',
    ]
);
edited Apr '19

I already use filters, but I think callback should be implemented in Phalcon if it does not exist.

there is no option for a callback but you can use Resultset::filter()

$company
//or
$robot = Robots::findFirst();
$parts = $robot->parts; // get relation

// apply callback on each result
$filtered = $parts->filter(
   function ($part) {
       if ($part->id < 3) {
          // you can return an object or an array otherwise will be discarded
           return $part;
       }
   }
);

$filtered is an array with returned values from the callback

you can apply conditions on the qurey for all relation calls like:

$this->hasMany(
   'id', 
   'Invoices', 
   'inv_id', 
   [
       'alias'    => 'InvoicesUnpaid',
       'params'   => [
           'conditions' => "inv_status <> :status:",
           'bind' => ['status' => 'unpaid']
       ]
   ]
);

or you can apply conditions on specific call like:

$unpaidInvoices = $company->getRelated(
   'Invoices', 
   [
       'conditions' => "inv_status = 'paid'",
       'order'      => 'inv_created_date ASC',
   ]
);


8.4k
edited Apr '19

can you give an example for some operation on fetched results

if you want something like to convert a 1 to true for example you can use setters and getters to do that

edited Apr '19

When fetching related model results, I need to extract only some attributes, not all, I wrote a new method inside related model to fetch the necessary attributes. forexample: Parent model:

class Parent
{
    public function initialize()
    {
        $this->hasMany('id', Related::class, 'parent_id', ['alias' => 'related', 'callback' => 'toMinimizedArray']);
    }
}

Related model

class Related
{
    public $field1;
    public $field2;
    public $field3;
    public $field4;

    public function toMinimizedArray()
    {
        // just need two fields to be returned when fetching results
        return array_merge(parent::toMinimized(), [
            'field1' => $this->field1,
            'field2' => $this->field2
        ];
    }
}

can you give an example for some operation on fetched results

if you want something like to convert a 1 to true for example you can use setters and getters to do that



8.4k

if you mean by attributes table columns you can do that with conditions for example:

$this->hasMany(
    'id', 
    'Invoices', 
    'inv_id', 
    [
        'alias'    => 'InvoicesUnpaid',
        'params'   => [
            'columns'    => 'field1, field2, field3'
        ]
    ]
);


8.4k

if i may ask why would you want a mapped model but not all columns are set?

Your solution is good, but If I may do some operations to the returned result but not for all columns, how it could be done ?

if i may ask why would you want a mapped model but not all columns are set?



8.4k
edited Apr '19

you can use setters and getters to do something like that

technically its for validation but we can do more with it

for example if have a table have a column is_finished which is tiny int can be 0 or 1

if you want to convert the value to true or false automatically we can implement the following:

in model: create a setter and getter for the column in model and protect the property

/**
 *
 * @var integer
*/
protected $is_finished;

public function getIsFinished()
{
    return boolval($this->is_finished);
}

public function setIsFinished($value)
{
    $this->is_finished = intval($value);
}

now you can use the setter and getter or not it will be converted automatically

$model = Model::findFirst();

$is_finished = $model->is_finished; // true|false

$is_finished = $model->getIsFinished(); // true|false

$model->is_finished = false; // 0

$model->setIsFinished(true); // 1

notice

if you want to do the same for converting json there is a bug in version 3.x which you cant use a setter with array ( json ) #13661

There is an afterFetch() method that gets called whenever a model is retrieved - but that's not context-sensitive, which is what I think you need.

Am I correct in understanding you want to be able to return a resultset of Model objects, but with those objects only having a subset of the properties normally available to the Model? I don't believe that's possible. If you want Models, you get all the data that comes with the Model's associated record. If you only want a subset of data, you'll have to specify the columns and be happy with a generic object.

I can't see why you would require the Models only have a subset of data though. You're not saving any queries and the time spent processing those few extra columns is infitesmally small.

Thanks for replying, I think "afterFetch" can do the job for now.

There is an afterFetch() method that gets called whenever a model is retrieved - but that's not context-sensitive, which is what I think you need.

Am I correct in understanding you want to be able to return a resultset of Model objects, but with those objects only having a subset of the properties normally available to the Model? I don't believe that's possible. If you want Models, you get all the data that comes with the Model's associated record. If you only want a subset of data, you'll have to specify the columns and be happy with a generic object.

I can't see why you would require the Models only have a subset of data though. You're not saving any queries and the time spent processing those few extra columns is infitesmally small.

For basics filter like those in example it's betters to just use params option.