Another Version 2 -> 3 question... This time with serializing a model.

This code worked in Phalcon 2.x to return a fully serialized object back to my UI:

    public function initialize()
    {
        $this->setSource("accounts");

        $this->hasOne('region_id','Region','code',['alias'=>'region']);
    }

    public function afterFetch() {
        $this->region = $this->region;
        $this->sdk_account = $this->getSDKAccount();
        $this->created = $this->getCreated();
        $this->updated = $this->getUpdated();
    }

    public function getCreated() {
        return gmdate('D M d Y H:i:s O',strtotime($this->created));
    }

    public function getUpdated() {
        return gmdate('D M d Y H:i:s O',strtotime($this->updated));
    }

    protected function getSDKAccount() {
        //code to get sdk account
    }

So, when I did Account::findFirst(...) it would return the account with the region, sdk_account, and the augmented created and updated values.

I'm trying to do the same thing in Phalcon 3 and I'm only getting the augmented created and updated values. The region and sdk_account attributes aren't appended to the Account when serializing/sending back to the UI.



10.2k
edited Sep '16

Then to send back to the UI, I'm basically doing this:

                $account = Account::findFirst([
                    "conditions" => "id = :id:",
                    "bind" => [
                        "id" => $this->id
                    ]
                ]);

        $response = new Response();
        $response->setHeader('Content-Type','application/json');
        $response->setJsonContent($account);
        $response->send();

It also doesn't seem to be adhering to the protected variables I have defined in the Account model. If it's protected, it shouldn't return that variable to the UI, correct?

edited Sep '16

What is this ? Why you are not using query builder ? Where is this serialization ? Because i don't quite see it.

Just use query builder, nice and easy :)

Also you have wrong relation. It should be belongsTo, not hasOne. hasOne is for accessing Account from Region.



10.2k

The first code block is a bit of my account model. I'm not using query builder because model relationships are easier to define, debug, and build. Account hasOne Region. That part is correct.

The serialization is happening when I call

    $response->setJsonContent($account); //serializes the Account object model into JSON

In the 2.0 version, I didn't use the Phalcon Response object. I simply did this:

    echo json_encode($account);
    die();

This approach doesn't work the same in 3.0 either.

What I'm really after is an easy way to return attributes that are not necessarily part of the model along with the json data to the UI. Right now, this data is related models and alternate date formats. It will eventually include calculated values and other things. Here is my entire account model, if that helps:

    <?php

    namespace Zipline\Models;

    use Phalcon\Mvc\Model;
    use Phalcon\DI;
    use Phalcon\Mvc\Model\Behavior\SoftDelete;

    class Account extends Model {

        protected $api_key;
        protected $private_key;
        protected $rpp;
        public $sdk_account;
        public $region;

        public function initialize() {
            $this->setSource("accounts");

            $this->addBehavior(new SoftDelete([
                'field' => 'deleted',
                'value' => gmdate('Y-m-d H:i:s')
            ]));

            $this->hasOne('region_id','Zipline\\Models\\Region','code',['alias'=>'region']);
        }

        public function afterFetch() {
            $this->region = $this->region;
            $this->sdk_account = $this->getSDKAccount();
            $this->created = $this->getCreated();
            $this->updated = $this->getUpdated();
        }

        public function beforeValidationOnCreate() {
            $this->created = gmdate('Y-m-d H:i:s');
            $this->updated = gmdate('Y-m-d H:i:s');
        }

        public function beforeValidationOnUpdate() {
            $this->updated = gmdate('Y-m-d H:i:s');
        }

        public function getCreated() {
            return gmdate('D M d Y H:i:s O',strtotime($this->created));
        }

        public function getUpdated() {
            return gmdate('D M d Y H:i:s O',strtotime($this->updated));
        }

        public function createAccount($request) {
            if ($sdk_account = $this->createSDKAccount($request)) {
                $this->region_id = $request->region;
                $this->sdk_id = $sdk_account->id;
                $this->company_name = $request->company_name;
                $this->api_key = $sdk_account->apiKey;
                $this->private_key = $sdk_account->privateKey;
                if ($this->save()) {
                    $account = Account::findFirst([
                        "conditions" => "id = :id:",
                        "bind" => [
                            "id" => $this->id
                        ]
                    ]);
                    $session = $this->getDi()->getShared('session');
                    //save the account in session so we can capture billing information, if needed, without logging the user in
                    //used by the billing models and PayPal library.
                    $session->set('signup_account',$account->toArray());
                    return $account;
                }
            }
            return false;
        }

        private function createSDKAccount($request) {
            $sdk = $this->getDi()->getShared("sdk");
            $params = [
                "name" => $request->company_name,
                "company" => $request->company_name,
                "contact" => $request->first_name." ".$request->last_name,
                "email" => $request->username,
                "url" => "http://",
                "region" => $request->region,
                "type" => "full",
                "level" => $request->subscription
            ];

            $sdk_response = $sdk->get("Create/Account",$params);

            if ($sdk_response->result != "success") {
                $this->appendMessage(new Message($sdk_response->message));
                return false;
            }

            return $sdk_response->account;
        }

        protected function getSDKAccount() {
            $session = $this->getDi()->getShared('session');
            $account = $session->get('sdk_account'.$this->id);

            if (!empty($account)) {
                return $account;
            }

            $sdk = $this->getDi()->getShared('sdk');

            $params = [
                "id" => $this->sdk_id
            ];

            $sdk_response = $sdk->get("View/Account",$params);

            if ($sdk_response->result != "success") {
                $this->appendMessage(new Message($sdk_response->message));
                return false;
            }

            unset($sdk_response->account->apiKey);
            unset($sdk_response->account->privateKey);

            $session->set('sdk_account'.$this->id,$sdk_response->account);

            return $sdk_response->account;
        }

    }
edited Sep '16

No, it's not good relation. hasOne is for accessing Account from Region. If in database in account table you have region_id then in your model it must be belongsTo relation.

Query builder is as well easy to managed, debug etc. This is just hacky version and also IT MAKES 2 QUERIES INSTEAD OF 1

Also if you are doing this in controller just such a code:

            $account = Account::findFirst([
                    "conditions" => "id = :id:",
                    "bind" => [
                        "id" => $this->id
                    ]
                ])->toArray();

        return $this->response->setJsonContent($account);

Is enough

Also your code is not working because in 3.0.x model is implementing JsonSerializable which is calling just toArray() method https://github.com/phalcon/cphalcon/blob/master/phalcon/mvc/model.zep#L4432

This was made i guess to make sure people don't do wrong things like you do.



10.2k

That doesn't make sense, semantically. If I'm an account I have one region. If I'm a region I belong to an account. What you're saying is the opposite of this.

I still don't understand what you think I'm doing wrong. What I'm doing worked well in 2.0.

Adding toArray() at the end of the findFirst isn't working for me to get the region and sdkaccount attributes appended to the account object that's sent back to the UI. I'm also still having the problem of the apikey and private_key being sent to the UI even though they are protected variables in the model.

The goal is to have this sent to the UI:

    'account': {
        /*account attributes with augmented dates and without the api_key or private_key*/
        'sdk_account': {
            /*sdk_account attributes*/
        },
        'region': {
            /*region attributes */
        },
    }

Again, this worked fine in 2.0. My entire app is built this way. I'm not sure why it's not working in 3.0. Is the problem in afterFetch? Has that implementation changed?



10.2k

...and I just looked up the docs for query builder and I'd much rather use model relationships to define my data.

edited Sep '16
          $account = Account::findFirst([
                    "conditions" => "id = :id:",
                    "bind" => [
                        "id" => $this->id
                    ]
                ])->toArrayWithRegion();
public function toArrayWithRegion()
{
    $array = $this->toArray();
    $array['region'] = $this->region->toArray();
    return $array;
}

But this will make additional query which is really bad. I would just change your logic and just use query builder. Why much rather ? I tihk query builder IS MUCH BETTER than model relationships.

Actually what you need is eager loading. There is just something in incubator for what you want, but as i told. For me eage loading is anti-pattern and should be avoided. Just query builder and join is really way to go.