yiisoft / yii-api

Yii REST API framework
BSD 3-Clause "New" or "Revised" License
93 stars 22 forks source link

Feature Request: Nested Resources / Routes #43

Closed saada closed 4 years ago

saada commented 9 years ago

In the Rails world they use "Resources" to describe nested routes. Here's a reference for further reading. http://guides.rubyonrails.org/routing.html#nested-resources

It would be great to add this feature to Yii2.

class Magazine < ActiveRecord::Base
  has_many :ads
end

class Ad < ActiveRecord::Base
  belongs_to :magazine
end
resources :magazines do
  resources :ads
end

The above code gives you these routes out of the box

GET      /magazines/:magazine_id/ads
GET      /magazines/:magazine_id/ads/new
POST     /magazines/:magazine_id/ads
usualdesigner commented 9 years ago

+1

usualdesigner commented 9 years ago

BTW some frontside REST libraries (like restangular) allow you to do it out the box. So, if this ability will be on the server side - it would be really cool.

mikehaertl commented 9 years ago

+1

samdark commented 9 years ago

Any suggestions for PHP syntax for the feature? i.e. how you want it to look in Yii terms when using it.

klimov-paul commented 9 years ago

This could be a special URL rule class, which accepts 2 config parameters: 'modelClass' and 'relations':

'rules' => [
    [
        'class' => 'yii\rest\NestedModelUrlRule',
        'modelClass' => 'app\models\Magazine',
        'relations' => ['ads'],
    ],
],
lynicidn commented 9 years ago

@klimov-paul :+1:

saada commented 9 years ago

@klimov-paul, I like that! I would suggest doing it at the controller level. Something along the below lines...

MagazineController extends Controller {
    public function resources() {
         return [
              AdController::className()
         ];
    }
}
AdController extends Controller {
    public function resources() {
         return [
              CommentController::className()
         ];
    }
}

The above could mean that you expose these routes:

/magazine/:magazine_id:/ads
/magazine/:magazine_id:/ad/:ad_id:/comments
/ad/:ad_id:/comments
/ad/:ad_id:/comment/4

NOTICE: plural vs singular routes

usualdesigner commented 9 years ago

@klimov-paul it looks very good. It would be great to use arrays instead of string if we nee. E.g.

'rules' => [
    [
        'class' => 'yii\rest\NestedModelUrlRule',
        'modelClass' => 'app\models\Magazine',
        'relations' => [
            'ads' => [
                'relations' => [
                    'comments'
                ]
            ]
        ],
    ],
],
saada commented 9 years ago

@usualdesigner , would your example allow all these routes to be exposed?

/magazine/:magazine_id:/ads
/magazine/:magazine_id:/ad/:ad_id:/comments
/ad/:ad_id:/comments
/ad/:ad_id:/comment/4

or just these two?

/magazine/:magazine_id:/ads
/magazine/:magazine_id:/ad/:ad_id:/comments

Also, it's weird to mention some classes by namespace app\models\Magazine and others by just alias ads or comments. Thoughts on this?

saada commented 9 years ago

As I was reading the rails docs, I found another interesting, and related concept is Shallow Nesting and Routing Concerns. http://guides.rubyonrails.org/routing.html#routing-concerns

klimov-paul commented 9 years ago

This matter concerns not only URL routes but controller actions as well. I am afraid we are outmatched against Ruby here.

tunecino commented 9 years ago

any progress on this ?

A POST to /magazine/:magazine_id:/ads should correctly assign $ads->magazine_id before saving the new created data for example. Especial exception may be thrown if no magazine exist with magazine_id value ...

By initialising a modelClass instance in yii\rest\NestedModelUrlRule is that also mean modifying the activeController class by serving its actions different dataProvider instances ? or it is about building a new class to handle CRUD's ?

@klimov-paul do you have a suggestion about how to properly relate models if using this special URL rule class ?

cebe commented 9 years ago

related/duplicate of #9474

saada commented 9 years ago

Here's my API update leveraging existing relationships in the models.

Code

Introducing the new Controller::resource($className, $relationship) syntax

// controllers/MagazineController.php
MagazineController extends Controller {
    public function actionAds() {
         return $this->resource(Magazine::className(), 'ads');
    }
}

// controllers/AdController.php
AdController extends Controller {
    public function actionMagazine() {
         return $this->resource(Ad::className(), 'magazine');
    }
}

// models/Magazine.php
Magazine extends ActiveRecord {
    public function getAds() {
         return $this->hasMany(Ad::className(), ['id' => 'ad_id']);
    }
}

// models/Ad.php
Ad extends ActiveRecord {
    public function getMagazine() {
         return $this->hasOne(Magazine::className(), ['ad_id' => 'id']);
    }
}

Routes

HTTP Verb Path Controller#Action Used for
GET /magazines/:magazine_id/ads AdsController::actionIndex display a list of all ads for a specific magazine
GET /magazines/:magazine_id/ads/create AdsController::actionCreate return an HTML form for creating a new ad belonging to a specific magazine
POST /magazines/:magazine_id/ads AdsController::actionCreate create a new ad belonging to a specific magazine
GET /magazines/:magazine_id/ads/:id AdsController::actionView display a specific ad belonging to a specific magazine
GET /magazines/:magazine_id/ads/:id/update AdsController::actionUpdate return an HTML form for editing an ad belonging to a specific magazine
PATCH/PUT /magazines/:magazine_id/ads/:id AdsController::actionUpdate update a specific ad belonging to a specific magazine
DELETE /magazines/:magazine_id/ads/:id AdsController::actionDelete delete a specific ad belonging to a specific magazine

I think this approach is a lot more familiar/flexible. @samdark , @klimov-paul, @mikehaertl ... what do you guys think?

homebru commented 9 years ago

+1

saada commented 9 years ago

I also found this feature in Laravel: http://laravel.com/docs/5.1/controllers#restful-resource-controllers

fernandezekiel commented 9 years ago

here's my suggested interface for modifying the query or data https://github.com/yiisoft/yii2/issues/7184#issuecomment-73396628 however it is more generic and doesn't exactly do it as relations

the advantage of my approach is that for example

GET /magazines/1234

returns a 403 only for that specific 1234 ID for the magazine then

GET /magazines/1234/ads/456 or /magazines/1234/ads 

will also return a 403

now i am not sure if this is a desired behavior or not

m-matveev commented 8 years ago

+1, also you can just write custom urlrule... http://stackoverflow.com/questions/27798088/yii2-nested-resources-best-practice

tunecino commented 8 years ago

Here is an extension doing that: https://github.com/tunecino/yii2-nested-rest

razonyang commented 7 years ago

+1

SamMousa commented 5 years ago

Should this be moved to https://github.com/yiisoft/yii-rest?

samdark commented 5 years ago

Yes.

samdark commented 4 years ago

Currently it seems to be out of our scope.