Convert Model With Relations To Php Array And Json

A function for convert model to array.

This function convert a model with all relations data to array.

You can use the result of this function in CJSON::encode() to get json result in client side.




    public function convertModelToArray($models) {

        if (is_array($models))

            $arrayMode = TRUE;

        else {

            $models = array($models);

            $arrayMode = FALSE;

        }


        $result = array();

        foreach ($models as $model) {

            $attributes = $model->getAttributes();

            $relations = array();

            foreach ($model->relations() as $key => $related) {

                if ($model->hasRelated($key)) {

                    $relations[$key] = convertModelToArray($model->$key);

                }

            }

            $all = array_merge($attributes, $relations);


            if ($arrayMode)

                array_push($result, $all);

            else

                $result = $all;

        }

        return $result;

    }



and use it like




public function actionComment() {

$db = Comment::model()->with('user')->findAll();

echo CJSON::encode(convertModelToArray($db));

}



Nice job!Appreciated.

really nice, thanks!

Nice work … keep up all your good work …

And if you need to filter attributes in your array you can use this :




    public static function convertModelToArray($models, array $filterAttributes = null) {

        if (is_array($models))

            $arrayMode = TRUE;

        else {

            $models = array($models);

            $arrayMode = FALSE;

        }


        $result = array();

        foreach ($models as $model) {

            $attributes = $model->getAttributes();


            if (isset($filterAttributes) && is_array($filterAttributes)) {

                foreach ($filterAttributes as $key => $value) {


                    if (strtolower($key) == strtolower($model->tableName()) && strpos($value, '*') === FALSE) {

                        $value = str_replace(' ', '', $value);

                        $arrColumn = explode(",", $value);


                        foreach ($attributes as $key => $value)

                            if (!in_array($key, $arrColumn))

                                unset($attributes[$key]);

                    }

                }

            }


            $relations = array();

            foreach ($model->relations() as $key => $related) {

                if ($model->hasRelated($key)) {

                    $relations[$key] = self::convertModelToArray($model->$key, $filterAttributes);

                }

            }

            $all = array_merge($attributes, $relations);


            if ($arrayMode)

                array_push($result, $all);

            else

                $result = $all;

        }

        return $result;

    }



and use it like




public function actionComment() {

$db = Comment::model()->with('user','message')->findAll();

echo CJSON::encode(convertModelToArray($db,array('user'=>'id,firstname','message'=>'*')));

}



Hi farhad2161,

Your code works well, but the filter is not working with HAS_MANY relation.

I had the same problem with CarJSON (http://www.yiiframework.com/forum/index.php/topic/36644-carjson-extension-problem/).

Any idea?

Hi

Please try this one:




    /**

     * Converting a Yii model with all relations to a an array.

     * @param mixed $models A single model or an array of models for converting to array.

     * @param array $filterAttributes should be like array('table name'=>'column names','user'=>'id,firstname,lastname'

     * 'comment'=>'*') to filter attributes.

     * @return array array of converted model with all related relations.

     */

    public static function convertModelToArray($models, array $filterAttributes = null) {

        if (is_array($models))

            $arrayMode = TRUE;

        else {

            $models = array($models);

            $arrayMode = FALSE;

        }


        $result = array();

        foreach ($models as $model) {

            $attributes = $model->getAttributes();


            if (isset($filterAttributes) && is_array($filterAttributes)) {

                foreach ($filterAttributes as $key => $value) {


                    if (strtolower($key) == strtolower($model->tableName())) {

                        $value = str_replace(' ', '', $value);

                        $arrColumn = explode(",", $value);


                        if (strpos($value, '*') === FALSE) {

                            $attributes = array();

                        }


                        foreach ($arrColumn as $column) {

                            if ($column != '*') {

                                $attributes[$column] = $model->$column;

                            }

                        }

                        //foreach ($attributes as $key => $value) {

                        //if (!in_array($key, $arrColumn))

                        //unset($attributes[$key]);

                        //}

                    }

                }

            }


            $relations = array();

            foreach ($model->relations() as $key => $related) {

                if ($model->hasRelated($key)) {

                    $relations[$key] = self::convertModelToArray($model->$key, $filterAttributes);

                }

            }

            $all = array_merge($attributes, $relations);


            if ($arrayMode)

                array_push($result, $all);

            else

                $result = $all;

        }

        return $result;

    }



Hi farhad2161,

The filter still not working with HAS_MANY relations, only with others.

I’m working on an example to demonstrate.

Thanks!

This code does not work with a STAT type of relation. Any solutions for this?

‘lastCookDate’ => array(self::STAT,

    'Lunchchefs', 


    'user_id',


    'select' => 'MAX(lunchevents.date)',


    'join' => 'INNER JOIN lunchevents ON t.lunch_id = lunchevents.lunch_id'),

Thanks!

Paul

Doesn’t work if one join results with NULL.

Try jsonize extension (www.yiiframework.com/extension/jsonize), it might handle your case

I fixed this bug and also add a parameter to let you exclude some relations when convert to array.

try this one:




<?php




/**

 * Description of JSONUtil

 *

 * @author 

 */

class JSONUtil {

     /**

     * Converting a Yii model with all relations to a an array.

     * @param mixed $models A single model or an array of models for converting to array.

     * @param array $filterAttributes should be like array('table name'=>'column names','user'=>'id,firstname,lastname'

     * 'comment'=>'*') to filter attributes.

     * @param array $ignoreRelations an array contains the model names in relations that will not be converted to array

     * @return array array of converted model with all related relations.

     */

    public static function convertModelToArray($models, array $filterAttributes = null,array $ignoreRelations=array()) {

        if((!is_array($models))&&(is_null($models))) return null;


        if (is_array($models))

            $arrayMode = TRUE;

        else {

            $models = array($models);

            $arrayMode = FALSE;

        }


        $result = array();

        foreach ($models as $model) {

            $attributes = $model->getAttributes();


            if (isset($filterAttributes) && is_array($filterAttributes)) {

                foreach ($filterAttributes as $key => $value) {


                    if (strtolower($key) == strtolower($model->tableName())) {

                        $value = str_replace(' ', '', $value);

                        $arrColumn = explode(",", $value);


                        if (strpos($value, '*') === FALSE) {

                            $attributes = array();

                        }


                        foreach ($arrColumn as $column) {

                            if (($column!='')&&($column != '*')) {

                                $attributes[$column] = $model->$column;

                            }

                        }

                        //foreach ($attributes as $key => $value) {

                        //if (!in_array($key, $arrColumn))

                        //unset($attributes[$key]);

                        //}

                    }

                }

            }


            $relations = array();

            $key_ignores = array();


            if($modelClass = get_class($model)){

                if(array_key_exists($modelClass,$ignoreRelations)){

                    $key_ignores = explode(',',$ignoreRelations[$modelClass]);

                }

            }


            foreach ($model->relations() as $key => $related) {


                if ($model->hasRelated($key)) {

                    if(!in_array($key,$key_ignores))

                            $relations[$key] = self::convertModelToArray($model->$key, $filterAttributes,$ignoreRelations);

                }

            }

            $all = array_merge($attributes, $relations);


            if ($arrayMode)

                array_push($result, $all);

            else

                $result = $all;

        }

        return $result;

    }

}



Nice

Great function. I also needed the column names of the output to differ from the actual database ones so I extended the function to support aliases using the $filterAttributes argument. It works just like aliases in SQL. For example if $filterAttributes = array(‘user’ => ‘n AS name’) would result in ‘name’ being the key in the returned array rather than ‘n’.




<?php

/**

 * Description of JSONUtil

 *

 * @link http://www.yiiframework.com/forum/index.php/topic/41922-convert-model-with-relations-to-php-array-and-json/

 * @author

 */

class JSONUtil {

     /**

     * Converting a Yii model with all relations to a an array.

     * @param mixed $models A single model or an array of models for converting to array.

     * @param array $filterAttributes should be like array('table name'=>'column names','user'=>'id,firstname,lastname'

     * 'comment'=>'*') to filter attributes. Also can use alias for column names by using AS with the column name just

     * like in SQL.

     * @param array $ignoreRelations an array contains the model names in relations that will not be converted to array

     * @return array array of converted model with all related relations.

     */

    public static function convertModelToArray($models, array $filterAttributes = null,array $ignoreRelations=array())

    {

        if((!is_array($models))&&(is_null($models))) return null;


        if (is_array($models))

            $arrayMode = TRUE;

        else {

            $models = array($models);

            $arrayMode = FALSE;

        }


        $result = array();

        foreach ($models as $model) {

            $attributes = $model->getAttributes();


            if (isset($filterAttributes) && is_array($filterAttributes)) {

                foreach ($filterAttributes as $key => $value) {


                    if (strtolower($key) == strtolower($model->tableName())) {

                        $arrColumn = explode(",", $value);


                        if (strpos($value, '*') === FALSE) {

                            $attributes = array();

                        }


                        foreach ($arrColumn as $column)

                        {

                            $columnNameAlias = array_map('trim', preg_split("/[aA][sS]/", $column));


                            $columnName = '';

                            $columnAlias = '';


                            if(count($columnNameAlias) === 2)

                            {

                                $columnName = $columnNameAlias[0];

                                $columnAlias = $columnNameAlias[1];

                            }


                            else

                            {

                                $columnName = $columnNameAlias[0];

                            }


                            if(($columnName != '') && ($column != '*'))

                            {

                                if($columnAlias !== '')

                                {

                                    $attributes[$columnAlias] = $model->$columnName;

                                }


                                else

                                {

                                    $attributes[$columnName] = $model->$columnName;

                                }

                            }

                        }

                    }

                }

            }


            $relations = array();

            $key_ignores = array();


            if($modelClass = get_class($model)){

                if(array_key_exists($modelClass,$ignoreRelations)){

                    $key_ignores = explode(',',$ignoreRelations[$modelClass]);

                }

            }


            foreach ($model->relations() as $key => $related) {


                if ($model->hasRelated($key)) {

                    if(!in_array($key,$key_ignores))

                            $relations[$key] = self::convertModelToArray($model->$key, $filterAttributes,$ignoreRelations);

                }

            }

            $all = array_merge($attributes, $relations);


            if ($arrayMode)

                array_push($result, $all);

            else

                $result = $all;

        }

        return $result;

    }

}



[font="Arial"][size="3"]There is a little gotcha in the filterAttributes variable:[/size][/font]

[font="Arial"][size="3"]Make sure you use the table name of the relation - not the relation name.[/size][/font]

[font="Arial"][size="3"]So, for


HAS_ONE approver => 'User'

you need to the "{{user}}" as the key like this:[/size][/font]

[font="Arial"][size="3"]




$models = Post::model()->with( array('owner','approver') )->findAll();

$ret = $this->convertModelToArray( $models, array(

	'{{user}}'=>'id,username',

);



[/size][/font]

It’s an ugly one, but rather than complicating it and setting up different virtual attributes, include them in a fuzzy way in the result of ->getAttributes() I chose to amend the code with a small if checking specifically for the STAT relation:


public function convertModelToArray($models) {

        if (is_array($models))

            $arrayMode = TRUE;

        else {

            $models = array($models);

            $arrayMode = FALSE;

        }


        $result = array();

        foreach ($models as $model) {

            $attributes = $model->getAttributes();

            $relations = array();     

            foreach ($model->relations() as $key => $related) {

                if ($model->hasRelated($key)) {

                    if ($related[0] == "CStatRelation")

                        $relations[$key] = $model->$key;

                    else

                        $relations[$key] = $this->convertModelToArray($model->$key);

                }

            }

            $all = array_merge(array_filter($attributes,'count'), array_filter($relations,'count'));


            if ($arrayMode)

                array_push($result, $all);

            else

                $result = $all;

        }

        return $result;

    }

Btw I also use array_filter with the callback of ‘count’ to check not include the fields that are “” or NULL but leave and include the ‘0’ (zero int).

Very useful …Thank you