Deserialization of model fields when we select multiple records

I have next model:


class Team extends \Phalcon\Mvc\Model
{
    /** @var int */
    public $id;
    /** @var array */
    public $players;

    public function beforeSave()
    {
        $this->players = serialize($this->players);
    }

    public function afterFetch()
    {
        $this->players = unserialize($this->players);
    }
}

When I save item and get them via Team::findFirst() then array deserializes as expected:


$team = new Team();
$team->players = [777,888];
$team->save();
$id = $team->id; // 1
json_encode(Team::findFirst($id)); // {"id":"1","players":[777,888]} 

But when I use Team::find() to obtain collection of objects, behavior is different, array is not deserialized:


json_encode(Team::find($id)->toArray()); //  [{"id":"1","players":"a:2:{i:0;i:777;i:1;i:888;}"}]

How to tell Phalcon to deserialize it on collection fetch like this?

It will be serialized once you access this item in collection object or return it.

I know this, but is this the only option? I.e.


json_encode(Team::find()->toArray()); //  [{"id":"1","players":"a:2:{i:0;i:777;i:1;i:888;}"}]

must be replaced with


$teams = [];
$teamsFetchResult = Team::find()->toArray();
foreach ($teamsFetchResult as $teamItem) {
    $teams[] = $teamItem;
}

json_encode($teams); //  [{"id":"1","players":[777,888]}]

You do realise you can store JSON data directly in SQL? Use a JSON data type.

Yes but it's not related with my question. I need to save collection and need to fetch it back later. If I save array, I want to get it back as array, not something else. JSON output is for demonstration reasons. Moreover MySQL 5.7 where JSON data type was implemented still not spread widely.

I was pointing out that SQL isn't great for storing serialised data.

Which version of Phalcon are you using? Does the afterFetch() method work correctly when using:

$team = Team::query()
            ->where('id', $id)
            ->execute()
            ->toArray();
json_encode($team);

If not, I'm guessing the toArray() is the culprit...

edited Apr '16

Team::find() or

Team::query()
            ->where('id', $id)
            ->execute()

Return resultset which can be accessed in array access. Object is deserialized once you access it from resultset. I guess there should be some additional parameter in toArray() method which will cause deserialization or just toArray should deserialize all objects in resultset.

edited Apr '16

I'm using Phalcon 2.0.9. And yes, I also have investigated that it's toArray strange behavior.


$team = Team::query()
            ->where('id', $id)
            ->execute()
            ->toArray();
json_encode($team); // also outputs [{"id":"1","players":"a:2:{i:0;i:777;i:1;i:888;}"}]

toArray is not deserializes correctly.

But "touching" all items in loop like this


$teams = [];
$teamsFetchResult = Team::find();
foreach ($teamsFetchResult as $teamItem) {
    $teams[] = $teamItem;
}

to get it's real values is too hacky and ugly to be right solution.

edited Apr '16

toArray method don't deserialize at all.

Ok, but what method do it? Obtaining collection of normal objects is very basic functionality of any ORM and will do without any tricks.

edited Apr '16

@andresgutierrez is this an oversight or intentional?

It's itentional i guess. But i will do PR for discussion with ability to fire afterFetch method when doing toArray on resultset/simple only.

Done PR with it:

https://github.com/phalcon/cphalcon/pull/11627

The problem with it is that we need to iterate over each row, and fire somehow aftetFetch method from our class. I did it with reflection method, beacause using cloneResultMap was like 5 times slower.

Ok, thank you. As temporary workaround with automatic typing I have implemented the next model base class. It may be useful for someone who facing into the same problem.


class BaseModel extends \Phalcon\Mvc\Model
{
    protected $_serializable = [];
    protected $_bool = [];
    protected $_int = [];
    protected $_float = [];

    public function beforeSave()
    {
        foreach ($this->_serializable as $fieldName) {
            $this->$fieldName = json_encode($this->$fieldName);
        }
        foreach ($this->_bool as $fieldName) {
            $this->$fieldName = (int) $this->$fieldName;
        }
        foreach ($this->_int as $fieldName) {
            $this->$fieldName = (int) $this->$fieldName;
        }
        foreach ($this->_float as $fieldName) {
            $this->$fieldName = (float) $this->$fieldName;
        }
    }

    public function afterFetch()
    {
        foreach ($this->_serializable as $fieldName) {
            $this->$fieldName = json_decode($this->$fieldName);
        }
        foreach ($this->_bool as $fieldName) {
            $this->$fieldName = (bool) $this->$fieldName;
        }
        foreach ($this->_int as $fieldName) {
            $this->$fieldName = (int) $this->$fieldName;
        }
        foreach ($this->_float as $fieldName) {
            $this->$fieldName = (float) $this->$fieldName;
        }
    }

    /**
     * @param int $id
     * @return \Phalcon\Mvc\Model
     * @throws Exception
     */
    public static function getById($id)
    {
        $item = self::findFirst($id);
        if (!$item) {
            throw new \Exception('Item is not found');
        }
        return $item;
    }

    /**
     * @return array
     */
    public static function getAll()
    {
        $items = [];
        $all = self::find();
        foreach ($all as $item) {
            $items[] = $item;
        }
        return $items;
    }
}

Inheritance example:


class Team extends \BaseModel
{
    protected $_int = ['id'];
    protected $_serializable = ['players'];

    /** @var int */
    public $id;
    /** @var array */
    public $players;
}

Usage:


Team::getAll(); // [{"id":1,"players":[777,888]}]

Team::getById(1); // {"id":1,"players":[777,888]}

Team::findFirst(1); // {"id":1,"players":[777,888]}