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

Facades to Models in API?

Hello!

I'm using Phalcon 2 and \Phalcon\Mvc\Micro as a start for my API and need best practices.

For example, i have models Users, Cars, Settings, Contacts. I need to give out a completely different (but stable) structure. Then, i think, i need some facade into Users model that covers all use cases like CRUD, advanced searching (by parameters too) and so on.

I've tried getters but they don't solve problem cuz i need to use normal models in code. Facade just for response to client. And (important!) able to create different independent facades for different clients (e.g., iOS app, website, smth else). Plus, i can in this case better optimize queries to database (if i know that i need user country and city on every request for iOS app only but not for website).

Right now i have method toAPI() in each model of my app when i'm doing it by my hand. This is problem cuz i need to proxy data from request into models from controllers and they are may be different. On the other hand, i'm trying to implement this logic in \App\Controllers\Rest (that is base for others excepth \App\Controllers\Auth and so on) but have too much duplicate code or too much complexity and very low readability, or too much is..else/switch..case.

How do you solve same problems? Maybe, Phalcon-specific library, book, example application source or smth similar?

Thanks in advance!

ps: sorry for my, possibly bad, english ):

For the sake of clarity, could you please post something of the code you're expecting to work?



21.7k
edited May '15

If we have User model with cars relation (one to many) then i need to send in response:

  • Always attach cars array to user (but in code before response i don't need them at all)
  • Always send settings as key-value object, but Settings is another model with another fields (id, userId, key, value, status)
  • Send some fields in another format that used by internal code (e.g. send "human readable" datetime instead of iso8601, used everywhere in code)

I need some facade/service that will allow me to set response logic inside it and not write garbase in models or controllers. Without them i got very strange and bad code ): Need help here.

Example.

class \Api\Models\Users extends \Phalcon\MVC\Model
{
    protected $id;
    protected $email;
    protected $username;
    protected $password;
    protected $status;
    protected $registered;

    const STATUS_ACTIVE = 1;
    const STATUS_DELETED = 0;

    /* bunch of setters/setters here like Users::getUsername() */

    public function getSettings()
    {
        $settings = Setting::find("userId = $this->id AND status = " . self::STATUS_ACTIVE);
        if (!$settings) return [];

        // What i need in internal code
        return $settings;

        // What users of my API expecting
        $map = [];
        foreach ($settings as $setting)
            $map[$setting->key] = $setting->value;
    }

    public function getRegistered()
    {
        // What i need in internal code
        return $this->registered;

        // What users of my API expecting
        // This may change fglobally for set of models or even set for every user differently
        return (new \DateTime($this->registered))->format(\DateTime::ISO8601);
    }

    public static function getFieldsVisibleForExternal()
    {
        return [
            // Own fields
            'username',
            'status',
            // Formatted
            'registered',
            // Relations
            'cars',
            'settings',
        ];
    }
}
class \Api\Models\Settigs extends \Phalcon\MVC\Model
{
    protected $userId;
    protected $key;
    protected $value;
    protected $status;

    /* bunch of setters/setters here like Settings::getKey() */
}
class \Api\Controllers\Users extends \Phalcon\Mvc\Controller
{
    public function search()
    {
        $carsFiltersAllowed = [
            'id' => 'int',
            'model' => ['trim', 'string'],
            'driver' => 'int',
        ];
        $carsFilters = [];
        foreach ($carsFiltersAllowed as $field => $type)
            $carsFilters[$field] = $this->request->get($field, $type);

        /* ... Users filters here too ... */

        // Now, getting data, yay!
        $users = Users::findByAttributes($filters);
        if (!$users) return [];

        // What i want to do
        return $users;

        // Or, maybe this?
        return $users->scope('external')->with(['cars', 'settings']);

        // What i need to do cuz users expecting another format )-:
        $totalUsersArray = [];
        foreach ($users as $user) {
            $tmp[] = $user->toArray();
            /**
             * I can't just use $user->cars because it will be empty after $user->toArray().
             *
             * Plus, there is strange behaviuor when i try to use this feature
             * Related: https://forum.phalcon.io/discussion/3867/preloading-model-relations
             * 
             * Also, Cars model can have same logic and i need to do something similar for them too
             * But not fall in recursion for $car->getDriver()!
             */
            $cars = $user->getCars($carsFilters);
            $tmp['cars'] = [];
            foreach ($cars as $car) {
                // Note that this same code will be used below (see comment there too)
                $tmp = $car->toArray();
                $carArray = [];
                foreach ($car::getFieldsVisibleForExternal() as $field)
                    $carArray[$car->id] = $tmp[$field];
                $tmp['cars'][$car->id] = $carArray;
            }

            // Getter solve this problem
            $tmp['setings'] = $user->getSettings();

            /**
             * Only whitelisted fields must be sent to response.
             * i can't use "$user->toArray(['settings'])" here
             * Because this will be empty )-:
             */
            $userArray = [];
            foreach ($user::getFieldsVisibleForExternal() as $field)
                $userArray[$user->id] = $tmp[$field];

            $totalUsersArray = $userArray;
        }

        // MAH AIS UR BLEEDING NOW
        return $totalUsersArray;
    }
}


21.7k
Accepted
answer

Kinda old question but i use now Fractal from the Php League that solves my issue.