Home > Enterprise >  How to correctly collect related data in Yii2 REST API?
How to correctly collect related data in Yii2 REST API?

Time:06-07

I have two ActiveRecord models: Category & Product with many-to-many relation (via ProductCategory model) & I'm trying to build REST API that will search & return products with name LIKE '%term%' grouped by categories.

My ApiController:

<?php

namespace api\modules\v1\controllers;

use api\modules\v1\models\Category;
use yii\data\ActiveDataProvider;
use yii\rest\Controller;
use Yii;
use yii\web\BadRequestHttpException;

class ApiController extends Controller
{
    const ITEMS_PER_PAGE = 50;

    public $serializer = [
        'class' => 'yii\rest\Serializer',
        'collectionEnvelope' => 'items',
    ];

    public function actionSearchProductsByTerm()
    {
        if (Yii::$app->request->get('term')) {
            return new ActiveDataProvider([
                'query' => Category::find()
                    ->with([
                        'products' => function ($query) {
                            /** @var $query yii\db\ActiveQuery */
                            $query->andWhere(['like', 'name', Yii::$app->request->get('term')]);
                        }
                    ]),
                'pagination' => [
                    'pageSize' => self::ITEMS_PER_PAGE,
                ],
            ]);
        } else {
            throw new BadRequestHttpException('Missing term parameter in request');
        }
    }
}

But when I request api.my-site.loc/v1/searchProductsByTerm?term=iphone&expand=products I see following result:

{
  "success": true,
  "data": {
    "items": [
      {
        "id": 2,
        "name": "Apple technics",
        "url": null,
        "products": []
      },
      {
        "id": 3,
        "name": "Apple smartphones",
        "url": null,
        "products": []
      },
      {
        "id": 4,
        "name": "Apple tablets",
        "url": null,
        "products": []
      },
      {
        "id": 5,
        "name": "Apple notebooks",
        "url": null,
        "products": []
      }
    ],
    "_links": {
      "self": {
        "href": "http://api.my-site.loc/v1/searchProductsByTerm?term=iphone&expand=products&page=1&per-page=50"
      },
      "first": {
        "href": "http://api.my-site.loc/v1/searchProductsByTerm?term=iphone&expand=products&page=1&per-page=50"
      },
      "last": {
        "href": "http://api.my-site.loc/v1/searchProductsByTerm?term=iphone&expand=products&page=1&per-page=50"
      }
    },
    "_meta": {
      "totalCount": 4,
      "pageCount": 1,
      "currentPage": 1,
      "perPage": 50
    }
  }
}

Question: why is a list of all categories on the site displayed, and not just those that contain products with name LIKE '%iphone%'? At the same time, the array of products is empty everywhere for some reason. What did I miss? And another question: is it possible to somehow return an array of products by default, so as not to specify expand=products every time (because for this API method this is redundant, since the search will always be performed by product attributes)?

CodePudding user response:

You're currently fetching all the categories and then adding the products. If you want to remove the categories that don't have matching products you should change to an inner join.

return new ActiveDataProvider([
    'query' => Category::find()
        ->innerJoinWith([
            'products' => function ($query) {
                return $query->where(['like', 'name', Yii::$app->request->get('term')]);
            }
        ])
    'pagination' => [
        'pageSize' => self::ITEMS_PER_PAGE,
    ],
]);

To return the products list by default you could add the products to the default fields. This will however always fetch the products when toArray() is called on a Category model. So you might want to be careful.

// In the Category model
public function fields() {
    $fields = parent::fields();
    $fields[] = 'products';
    return $fields;
}

You could also add the products as an expand to the get parameters by default for this specific request. Needs a bit of cleaning perhaps, but could be something like:

    $queryParams = Yii::$app->request->getQueryParams();
    $currentExpand = explode(',', $queryParams['expand']);
    $currentExpand[] = 'products';
    $queryParams['expand'] = implode(',', $currentExpand);
    Yii::$app->request->setQueryParams($queryParams);
  • Related