Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Yii2 Active Record JSON response, type casting issue

Tags:

json

php

yii

yii2

I am facing weird issue in Yii2 I have a query which has one join with agent table and a (one to many) relation of jobs with task it works fine but issue is it returns everything in string. Below is the query:

 $query = self::find()
        ->select("job.*, agent.first_name,agent.last_name")
        ->leftJoin('agent', 'job.agent_id = agent.id')
        ->with('tasks')
        ->asArray()
        ->all();

and the JSON encoded result:

{
  "success": true,
  "data": [
  {
  "id": "10",
  "customer_id": "1",
  "job_type": "normal",
  "created": "2016-06-22 10:19:25",
  "first_name": "Shayan",
  "last_name": "",
  "tasks": [
    {
      "id": "10",
      "job_id": "10",
      "title": "bring food",
      "instruction": null,
      "created": "2016-06-22 10:19:25",

    },
    {
      "id": "10",
      "job_id": "10",
      "title": "bring pizza",
      "instruction": null,
      "created": "2016-06-22 10:19:25",

    },
  ]
}

if you notice the fields like id, customer_id and job_id these all are integer but it return as string. But if I remove ->asArray() from above query it return valid type casting but issue is it skips relational and leftJoin agent table fields, it only returns job table fields here is the response after removing ->asArray() from above query.

{
"success": true,
"data": [

 {
  "id": 10,
  "customer_id": 1,
  "name": null,
  "job_type": "normal",
  "created": "2016-06-22 10:19:25",
},

If you notice in above response it does not have agent tables first_name, last_name and relational data tasks completely skipped but id and customer_id is in integer.

Does anyone faced same issue? your help would be highly appreciated. Thanks in advance.

like image 219
Kamran Khatti Avatar asked Aug 02 '16 15:08

Kamran Khatti


3 Answers

I wanted to make sure that's actually the case. I have tested this myself with quite similar query and my results are quite similar:

array(2) {
  [0]=>
  array(5) {
    ["id"]=>
    string(1) "1"
    ["name"]=>
    string(5) "Admin"
    // ...
  }
// ...
}

In my case I also get all types as strings. So, If you're going to check for input and its type with if ($data[0]['id'] === 1), you will get false result since it's string.

But what you need to do is to add (int) before variable to convert it into different typecast. This would be: (int) $data[0]['id'].

Then var_dump((int) $data[0]['id']); (in my case) will give int(1) instead of string(1) "1".

You can also check in conditionals:

((int) $data[0]['id'] === 1) ? exit('Integer') : exit('Not integer');

Without writing (int) as prefix will give Not integer result while with prefix will yield Integer.

If you do not want to keep writing these prefixes in each function, you can write something like:

$data[0]['id'] = (int) $data[0]['id'];

And now $data[0]['id'] will be integer in future uses.


New solution:

This new solution will return an object with arrays instead of just arrays.

// Method that gives data back. In this case, user with ID == 10.
public static function getData()
{
    $dataProvider = new ActiveDataProvider([
        'query' => self::findOne(['id' => 10])->attributes
    ]);

    return $dataProvider;
}

In Controller you (as always) pass this object:

$data = User::getData();

return $this->render('user', [
            //...
            'data' => $data
        ]);

And then in Viewer you may access values (in correct typecast) like this:

$data->query['columnName'];

So, for ID check:

($data->query['id'] === 10 ? exit('ok') : exit('nok'));

You will get response ok (typecast: integer, value: 10).

like image 130
Gytis TG Avatar answered Sep 21 '22 06:09

Gytis TG


This is expected behavior and also documented:

Note: While this method saves memory and improves performance, it is closer to the lower DB abstraction layer and you will lose most of the Active Record features. A very important distinction lies in the data type of the column values. When you return data in Active Record instances, column values will be automatically typecast according to the actual column types; on the other hand when you return data in arrays, column values will be strings (since they are the result of PDO without any processing), regardless their actual column types.

For the second issue, to retrieve the additional fields in the active record class you have to create additional properties in the class for them:

class MyRecord extends \yii\db\ActiveRecord
{
    public $first_name;
    public $last_name;

    // ...
}
like image 43
cebe Avatar answered Sep 21 '22 06:09

cebe


Method "asArray" will convert type of all variables to string. Therefore you should not use this.

Right solution you can find in documentation: https://www.yiiframework.com/doc/guide/2.0/en/rest-resources#overriding-extra-fields

You need use "fields" and "expand" params in URI for API requests.

For example: http://localhost/posts?fields=id,title&expand=author

At first add relation to the model:

    /**
     * @return \yii\db\ActiveQuery
     */
    public function getAuthor()
    {
        return $this->hasOne(Profile::class, ['id' => 'author_id']);
    }

Also you need add fields and extraFields methods to the model:

public function fields()
    {
        return ['id', 'title'];
    }

    public function extraFields()
    {
        return ['author'];
    }

And change response:

public function actionIndex()
{
    return new ActiveDataProvider([
        'query' => Post::find(),
    ]);
}

P.S. Sorry for necroposting, but I had the same issue recently

like image 45
user2991379 Avatar answered Sep 22 '22 06:09

user2991379