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

Any way to add model attributes at runtime and get it back with toArray() ?

Hi all,

I feel really limiting that only attrbutes define in the model metadata are returned when using the model toArray() method. In many occasions (especially when returning json data via my API), I need the ability add culculated/computed data to my models before returning it. We can discuss the model philosophy and if it's right to store extra data in there, but let's consider the simple example below for a second:

$oClient = $oModel->findFind(1);
//... doing stuffs

// adding new attribute at runtime
$oClient->remaining_credits = $SomeOtherObject->calcCredit();   

//... doing stuffs
return $oClient->toArray()

That doesn't work, to array only returning the the attributes defined in the metaData and not "remaining_credits".

  • I could add the attribute in the model meta data and use the skip feature when saving/creating/updating. (https://docs.phalcon.io/en/latest/reference/models.html#skipping-columns) . But that's some code executed all the time, across all my app, when I may need to calculate the "remaining_credits" only once.

  • I could (and did till now) convert my models to arrays, to which I could easily add extar data if bneeded to. But I end up spending my time converting stuffs to array, and as a result my controlers mostly process arrays when I should be able to keep working with model till I spit the data out my api.

So I've started looking at creating a custom version of toArray() that would include "all the data" present in the current object, but I endup trying to separate meta-attribute, attributes added on the fly and all the other phalcon services (di, db...) stored in the model at initialisation... a big mess.

So does anyone has a solution for that ? Or is there something in Phalcon I missed that could let me do that ?

Cheers



7.1k

You can override toArray, call parent::toArray and then inject other properties from a static array of properties defined in the model...

Hi nsossonko,

The only working solution I have right now is to do something like this:

public function toFullArray()
{

    $obj =  (new \ReflectionObject($this))->getProperties(\ReflectionProperty::IS_PUBLIC);
    $aObjData = [];
    foreach($obj as $oProperty)
    {
        $aObjData[$oProperty->name] = $this->{$oProperty->name};
    }
    return $aObjData;
}

Since adding attributes on the fly creates by default public properties, it should work just fine. I could overwrite the toArray method with that, probably doing something like:

public function toArray()
{
    $obj =  (new \ReflectionObject($this))->getProperties(\ReflectionProperty::IS_PUBLIC);
    $aObjData = [];
    foreach($obj as $oProperty)
    {
        $aObjData[$oProperty->name] = $this->{$oProperty->name};
    }

    return array_merge($aObjData, parent::toArray());
}

Is that the only way ? No better/lighter solution ?

edited Aug '16

First of all - don't user reflections.

Doesn't better to do:

public function toArray()
{
    $array = parent::toArray();
    $columnMap = $this->getModelsMetaData()->getColumnMap($this);
    $objectVars = get_object_vars($this);
    $diff = array_diff_key($objectVars, $columnMap);
    $manager = $this->getModelsManager();
    foreach($diff as $key => $value) {
        if($manager->isVisibleModelProperty($this, $key)) {
            $array += [$key => $value];
        }
    }
    return $array;
}

Not sure, but something like this should be faster than reflections i think.

Also keep in mind this will work only form 3.0.0

Well you can edit your solutions to make use from $manager->isVisibleProperty() instead of reflections.

Thanks for that.

I'mnot totally sure using the manager and getModelsMetaData() will be much faster than reflection, I may benchmark that next week. No worries about Phalcon 3, I've upgraded my app 2 weeks ago as I moved to Ubuntu 16.

It would be nice to have that kind of method incorporated natively in Phalcon thouh. If not replacing toArray(), I'm sure that could be usefull to many to have a "toFullArray" like method containing all the data the model currenlty holds.



77.7k
Accepted
answer

Why use meta at all? You already have the keys from parent::toArray :P

public function toArray()
{
    $array = parent::toArray();
    $objectVars = get_object_vars($this);
    $diff = array_diff_key($objectVars, $array)
    $manager = $this->getModelsManager();
    foreach($diff as $key => $value) {
        if($manager->isVisibleModelProperty($this, $key)) {
            $array += [$key => $value];
        }
    }
    return $array;
}

Oh you right, i missed that XD

Hi,

I'm pretty happy with that. It's a lot slower than standard toArray(), but I'm not planning in using all the time, mostly at the end of some API calls. If someone wants to use that, a few things to know:

  1. it works on phalcon 3.1+ (was in 3.0.0 and isVisibleModelProperty was not available)
  2. it's about 8 times slower than std toArray
  3. it doesn't provide any type of binding or control over the data types... to use at your own risk.

It would be awesome if the ppl @Phalcon were coding that in zephir to speed it up, and add that to the model class ;- )

edited Aug '16

I think it can't be speeded up. Method calls = always slower, zephir can't fix things like this.