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

How to convert to json code before sending to client?

Recently I'm work on with the official example https://github.com/phalcon/rest-api.

A field of my database is stored with serialized data:

a:3:{s:3:"uid";i:7;s:4:"name";s:6:"mervin";s:6:"status";s:5:"login";}

If under pure PHP, I can use unserialized() and json_encode() functions to convert to the json string.

But how to convert this field to json string in therest-apiexample?

You get the data from your database. Unserialize it and it will become an array. You then set that array as the payload in your response from your controller. The API will handle the rest (converting it to JSON)

$data = unserialize($dbValue);

$this->response->setPayloadSuccess($data);

https://github.com/phalcon/rest-api/blob/master/library/Http/Response.php#L147



31.3k
edited May '20

Thanks for the answer. The problem is there is no code can modify a specified field, below is the controller:

<?php
declare(strict_types=1);

class GetController extends BaseController
{
    /** @var string */
    protected $model       = Companies::class;

    /** @var array */
    protected $includes    = [
        Relationships::INDIVIDUALS,
        Relationships::PRODUCTS,
    ];

    /** @var string */
    protected $resource    = Relationships::COMPANIES;

    /** @var array<string|boolean> */
    protected $sortFields  = [
        'id'      => true,
        'name'    => true,
        'address' => true,
        'city'    => true,
        'phone'   => true,
    ];

    /** @var string */
    protected $transformer = CompaniesTransformer::class;
}

and the BaseController: https://github.com/phalcon/rest-api/blob/master/api/controllers/BaseController.php

Could you please show me a more complete code of the GetController?



39.3k
Accepted
answer
edited May '20

I thought that you had a new controller.

To use the same controllers as the API but with different fields - in your case the serialized one - you will need to check the transformer classes. Those are the ones that pick the data up from the collection (that came from the database) and transform them to whatever the API needs.

https://github.com/phalcon/rest-api/blob/master/library/Transformers/BaseTransformer.php#L52

This is the base transfromer action. You can create a new transformer class for your usage, you can add a simple conditional there, or extend the current transformer class, override transform to do what you need. Something like this:

    public function transform(AbstractModel $model)
    {
        $modelFields     = array_keys($model->getModelFilters());
        $requestedFields = $this->fields[$this->resource] ?? $modelFields;
        $fields          = array_intersect($modelFields, $requestedFields);
        $data            = [];
        foreach ($fields as $field) {
            $data[$field] = $model->get($field);

            // This is the serialized field
            if ("my-field" === $field) {
                $data[$field] = json_encode(unserialize($data[$field]));
            }
        }

        return $data;
    }

You will of course need to refactor this to suit your needs, add more checks so that json_encode and unserialize do not fail based on data etc.

edited May '20

You can use afterFetch() function in the Model Class and there convert your data: unserialize, decode form JSON etc.



31.3k
edited May '20

Thanks!

I've created a new transformer class:

class ProblemsTransformer extends BaseTransformer
{
    ...

    public function transform(AbstractModel $model)
    {
        $data = parent::transform($model);

        foreach ($data as $field => $value)
        {
            if ("sample_input" == $field)
            {
                $data[$field] = str_replace("&#34;", "\"", $data[$field]);
                $data[$field] = json_encode(unserialize($data[$field]));
            }
        }

        return $data;
    }
}

And it seems works well, I can get the proper data, but I'm not sure it is the correct way to override the transform method.

I thought that you had a new controller.

To use the same controllers as the API but with different fields - in your case the serialized one - you will need to check the transformer classes. Those are the ones that pick the data up from the collection (that came from the database) and transform them to whatever the API needs.

https://github.com/phalcon/rest-api/blob/master/library/Transformers/BaseTransformer.php#L52



31.3k
edited May '20

Actually there are no any explicit declaration of the data members in the Model, like the code below.

I think I cant't use getter or setter to change the data members. How to deal with afterFetch()?


class Companies extends AbstractModel
{
    /**
     * Initialize relationships and model properties
     */
    public function initialize()
    {
        $this->setSource('co_companies');

        $this->hasMany(
            'id',
            Individuals::class,
            'companyId',
            [
                'alias'    => Relationships::INDIVIDUALS,
                'reusable' => true,
            ]
        );

        parent::initialize();
    }

    /**
     * Model filters
     *
     * @return array<string,string>
     */
    public function getModelFilters(): array
    {
        return [
            'id'      => Filter::FILTER_ABSINT,
            'name'    => Filter::FILTER_STRING,
            'address' => Filter::FILTER_STRING,
            'city'    => Filter::FILTER_STRING,
            'phone'   => Filter::FILTER_STRING,
        ];
    }

}

You can use afterFetch() function in the Model Class and there convert your data: unserialize, decode form JSON etc.

If the data comes from your Companies table, extend the CompaniesTransformerand you can override the transform as you do there.

The reason that we extend the CompaniesTransformer is because that transformer has some data already set up (relationships etc.) that you will probably need.

But yeah this is the way to do it.

Thanks!

I've created a new transformer class:

class ProblemsTransformer extends BaseTransformer
{
   ...

   public function transform(AbstractModel $model)
   {
       $data = parent::transform($model);

       foreach ($data as $field => $value)
       {
           if ("sample_input" == $field)
           {
               $data[$field] = str_replace("&#34;", "\"", $data[$field]);
               $data[$field] = json_encode(unserialize($data[$field]));
           }
       }

       return $data;
   }
}

And it seems works well, I can get the proper data, but I'm not sure it is the correct way to override the transform method.

I thought that you had a new controller.

To use the same controllers as the API but with different fields - in your case the serialized one - you will need to check the transformer classes. Those are the ones that pick the data up from the collection (that came from the database) and transform them to whatever the API needs.

https://github.com/phalcon/rest-api/blob/master/library/Transformers/BaseTransformer.php#L52

There are a couple of things you will need to do if you want to work with the model and take advantage of afterFetch.

The code uses the get and set methods in the model, which are created to silently sanitize values based on the getModelFilters

You can stop using that if you want and use the $model->$fieldname directly. Also in the afterFetch you can use the field directlyto perform the transformation, just as you have done in your transformer class. Note though, that you will also need to change you transformer class to no longer use the get method of the model and sanitize things on your own.

Going down this route will definitely be a lenghier trip than creating a new transformer.

I should create a small video tutorial for the rest api....

Actually there are no any explicit declaration of the data members in the Model, like the code below.

I think I cant't use getter or setter to change the data members. How to deal with afterFetch()?


class Companies extends AbstractModel
{
   /**
    * Initialize relationships and model properties
    */
   public function initialize()
   {
       $this->setSource('co_companies');

       $this->hasMany(
           'id',
           Individuals::class,
           'companyId',
           [
               'alias'    => Relationships::INDIVIDUALS,
               'reusable' => true,
           ]
       );

       parent::initialize();
   }

   /**
    * Model filters
    *
    * @return array<string,string>
    */
   public function getModelFilters(): array
   {
       return [
           'id'      => Filter::FILTER_ABSINT,
           'name'    => Filter::FILTER_STRING,
           'address' => Filter::FILTER_STRING,
           'city'    => Filter::FILTER_STRING,
           'phone'   => Filter::FILTER_STRING,
       ];
   }

}

You can use afterFetch() function in the Model Class and there convert your data: unserialize, decode form JSON etc.

edited May '20

Well im unfamiliar with the rest-api example, I thought Your model extends \Phalcon\Mvc\Model class, in that case, if your data to unserialize is in "serialized_data" column, You would do in Your model Class:

public function afterFetch()  {
    if (!empty($this->serialized_data)) {
        $this->serialized_data = \unserialize( $this->serialized_data );
    }
}

and stright after getting model from ORM you have object in "serialized_data" variable insted of string to unserialize. Althought I prefer to keep data in json, convert to stdClass by json_decode in afterFetch method, and then convert from stdClass to target class. It's much safer in situation when Your serialized object class definition has changed.



31.3k

Thanks a lot for helping me out, Mr.Big ~ :D video tutorial will be wonderful,but I think more functional examples will be usefull, for example the pagination. The rest-api project is cute and magic for me~ Thanks for the project!

There are a couple of things you will need to do if you want to work with the model and take advantage of afterFetch.

The code uses the get and set methods in the model, which are created to silently sanitize values based on the getModelFilters

You can stop using that if you want and use the $model->$fieldname directly. Also in the afterFetch you can use the field directlyto perform the transformation, just as you have done in your transformer class. Note though, that you will also need to change you transformer class to no longer use the get method of the model and sanitize things on your own.

Going down this route will definitely be a lenghier trip than creating a new transformer.

I should create a small video tutorial for the rest api....