I am creating REST API in YII2. I am getting all post data by calling get API
/post/
/post/1/
But I want to get user also who post that particular post.
for example I want data in below format
{
"id":"1",
"title":"kapil",
"content" : "test",
"user" : {
"username":"admin",
"first_name":"kapil",
"last_name":"sharma",
//blah blah
}
}
But response is
{
"id":"1",
"title":"kapil",
"content" : "test",
}
I used this tutorial for creating API.
Let's say in your post method you have the getIdUser() relation:
public function getIdUser() {
return $this->hasOne(User::className(), ['id' => 'user_id']);
}
In that model, you should make use of the extraFields() method, as follows:
public function extraFields() {
return [
'user' => 'idUser' // or the name you hasOne relation with user has
];
}
Then, you call your REST API with the expand parameter, specifying there which extraField details you'd like to include, in your case:
http://example.com/post/view?id=1&expand=user
Related
Question
How can i return a specific column from a table [within the API Controller] ? I used pluck but it removed the column name from the output. I need the column name to be included as well. The $product table i refer(Referred in my code below) is just a table with product stuffs like name, price, discount rate etc..
Brief description
Present API response sample -
{
"data" : [
"Graham",
"Marina Philip",
"David Doomer",
],
"message" : "",
"success" : true
}
Expected response -
[
{
"name": "Graham",
},
{
"name": "Marina Philip",
}, {
"name": "David Doomer",
},
]
API Route from the APIController :
Route::resource('searchlist', 'API\SearchlistAPIController');
Index function from my SearchlistAPIController.php [Specific function]
public function index(Request $request)
{
try{
$this->productRepository->pushCriteria(new RequestCriteria($request));
$this->productRepository->pushCriteria(new LimitOffsetCriteria($request));
$this->productRepository->pushCriteria(new ProductsOfFieldsCriteria($request));
if($request->get('trending',null) == 'week'){
$this->productRepository->pushCriteria(new TrendingWeekCriteria($request));
}
else{
$this->productRepository->pushCriteria(new NearCriteria($request));
}
$products = $this->productRepository->all();
} catch (RepositoryException $e) {
return $this->sendError($e->getMessage());
}
//Here i've got the value of the table $Product with a bunch of columns from my database..
$sendinger = $products->pluck('name');
//I'm trying to filter the columns send here. But i lost the column name as well.
return $this->sendResponse($sendinger->toArray(),'');
}
Contents of my sendResponse method:
public function sendResponse($result, $message) { return Response::json(ResponseUtil::makeResponse($message, $result)); }
Also, how can i remove this from my Json response ? :
"message" : "",
"success" : true
Off the top of my head, I believe $products->pluck('name')->toArray() will create return an indexed array. E.g. [ "Graham", "Marina Philip", "David Doomer" ]
What you could do is...
$sendinger = $products->pluck('name')->map(function($name) {
return [ 'name' => $name ];
});
return $this->sendResponse($sendinger->toArray(),'');
More about mapping here: https://laravel.com/docs/7.x/collections#method-map
Not tested but should steer you in the right direction.
For your other question (removing "success" and "message" from the response)...
It appears you are using the Laravel Generator ResponseUtil class which is adding the additional parameters. See source here: https://github.com/InfyOmLabs/laravel-generator/blob/7.0/src/Utils/ResponseUtil.php
Simply replace the last line:
return $this->sendResponse($sendinger->toArray(),'');
with
return response()->json( $sendinger->toArray() );
and that should do the trick!
I'm quite new to Laravel and I was not able to find the answer to this problem neither on Laravel docs, nor here.
I guess it's just a matter of how to search for it, cause I'm pretty sure it's a common case.
I have two models in relationship (this is a simplified case), I retrieve the info I need through a Resource file, but I'm not able to understand how to properly store or update info.
Here's a code example:
Models\Company.php
class Company extends Model
{
protected $fillable = [
'name', 'blablabla', 'country_id', 'blablabla2',
];
public function country() {
return $this->belongsTo(Country::class);
}
}
Models\Country.php
class Country extends Model
{
protected $fillable = [
'code', 'name', 'prefix', 'tax_code_id',
];
public function companies() {
return $this->hasMany(Company::class);
}
}
Then I have a CompanyController file to manage API requests:
Controllers\CompanyController.php
class CompanyController extends BaseController
{
public function index()
{
$companies = Company::paginate();
$response = CompanyResource::collection($companies)->response()->getData(true);
return $this->sendResponse($response, 'Companies retrieved successfully');
}
public function store(Request $request)
{
$input = $request->all();
$validator = Validator::make($input, $this->validation_rules);
if($validator->fails()){
return $this->sendError('Validation error.', $validator->errors());
}
$company = Company::create($input);
return $this->sendResponse($company->toArray(), 'Company added successfully.');
}
}
...
public function update(Request $request, Company $company)
{
$input = $request->all();
$validator = Validator::make($input, $this->validation_rules);
if($validator->fails()){
return $this->sendError('Validation error.', $validator->errors());
}
$company->update($input);
return $this->sendResponse($company->toArray(), 'Company updated successfully.');
}
And here the CompanyResource I'm using to display info as I need.
Resources/CompanyResource.php
class CompanyResource extends JsonResource
{
public function toArray($request)
{
return [
'id' => $this->id,
'name' => $this->name,
'blablabla' => $this->blablabla,
'country' => $this->country,
'blablabla2' => $this->blablabla2,
];
}
}
So when retrieving Companies (or single company) I get a nested JSON:
{
"id": "1",
"name": "something",
"blablabla": "blablabla",
"country": {
"id": "100",
"code": "MA",
"name": "Mars",
"prefix": "123",
"tax_code_id": "#$%"
},
"blablabla2": "blablabla2"
}
If I create or update a new company I send a payload that has the same structure of what I'm getting above, but if I edit country id value my company model doesn't get it.
PUT Api/companies/1
{
"name": "something",
"blablabla": "blablabla3",
"country": {
"id": "200",
"code": "JU",
"name": "Jupiter",
"prefix": "456",
"tax_code_id": "#=%"
},
"blablabla2": "blablabla2"
}
I'm expecting to update country_id field in companies table for record 1 so that it matches payload (so going from 100 to 200), but it's not happening.
I could edit frontend logic in order to send only country_id in payload since I'm not going to update countries table and all that additional info is redundant, but I'd like to know how to manage it in controller with Laravel.
Would you mind helping me? Thanks in advance.
If you want it to work with the code now, you need to have country_id in the root JSON object you are sending. As this is the way you would fill the id. This is not the best approach in my opinion, but this is why your update is not working at the moment.
{
"name": "something",
"blablabla": "blablabla3",
"country_id": 200,
...
I actually like the approach of sending complete objects. Commonly to fill id's is not good, as it can interfere with the way relations work. Laravel will set your relationships when you associate, if not you are not guaranteed to have the correct relationship after the fill.
Therefor i would fetch out the id and associate the country object with the company. In a logic similar to this.
// only fill non relation fields, fill used as save is needed after associate()
$company->fill($request->only(['name', 'blabla']));
$company->country()->associate(Country::find($request->get('country')['id']));
//associate does not save
$company->save();
I wrote a gist for this years ago that can relate any two models regardless of their relationship type. You just need to supply it with the name of the relationship method: https://gist.github.com/kmuenkel/055f107139d904e30810bf53750d9c6e
I have three Laravel models within a multitenanted app:
Tenant {}
Company {
public function tenant() {
return $this->belongsTo('Tenant');
}
public function group() {
return $this->belongsTo('Group');
}
}
Group {
public function companies() {
return $this->hasMany('Company');
}
public function tenant() {
return $this->belongsTo('Tenant');
}
}
I am building an admin interface for my app using Frozennode Administrator, so the super admin can change stuff and so on. I want to be able to change the group of a company:
'edit_fields' => [
],
'group' => [
'title' => 'Group',
'type' => 'relationship',
'name_field' => "title",
//'constraints' => ['tenant' => 'tenant']
]
]
But my constraint does not work. How can I limit the groups shown to those matching the tenant that the company belongs to?
This is relatively simple. Just define a new relationship in your Company model like so: (and use this instead of "group" in your administrator config file):
public function tenant_group() {
return $this->belongsTo('Group')->whereTenantId(Session::get('current_tenant_id'));
}
The above code assumes you have a session variable called "current_tenant_id" which determines the current users' tenant. You can replace that with something else, perhaps something like this:
Auth::getUser()['tenant_id']
I’ve recently discovered that presenters (like this one) implement the decorator pattern and are a great way to add fields and logic to existing Laravel models. Take the following example for my question below:
// Tack on a new readable timestamp field.
public function timeago()
{
return $this->object->created_at->whenForHumans();
}
// Wrap an existing field with some formatting logic
public function created_at()
{
return $this->object->created_at->format('Y-m-d');
}
I can then use these presenter fields in my view:
{{ $object->timeago }}
{{ $object->created_at }}
How would you implement the decorator pattern for an API that returns JSON responses rather than Blade views? In all the Laravel/JSON articles I have read, objects are immediately returned without undergoing any transformation / presenter logic. e.g.:
// converting a model to JSON
return User::find($id)->toJson();
// returning a model directly will be converted to JSON
return User::all();
// return associated models
return User::find($id)->load('comments')->get();
How can I implement presenter fields in my JSON response?
$object->timeago
$object->created_at
As you mentioned, User::all returns JSON, so do something like:
Some function to get data and return a decorated response:
public function index()
{
$news = News::all();
return $this->respond([
'data' => $this->newsTransformer->transformCollection($news->toArray())
]
);
}
The above function will call Transformer::transformCollection:
<?php namespace Blah\Transformers;
abstract class Transformer {
public function transformCollection(array $items)
{
return array_map([$this, 'transform'], $items);
}
public abstract function transform($item);
}
which in turn will call NewsTransformer::transform():
public function transform($news)
{
return [
'title' => $news['title'],
'body' => $news['body'],
'active' => (boolean) $news['some_bool'],
'timeago' => // Human readable
'created_at' => // Y-m-d
];
}
The end result being JSON with the format you require, in this case:
{
data: {
title: "Some title",
body: "Some body...",
active: true,
timeago: "On Saturday, 1st of March",
created_at: "2014-03-01"
}
}
By the way, Laracasts has an excellent series on building APIs -- hope that helps!
For clarity, the respond function in the first code snippet just wraps the data with a status code, and any headers, something like:
return Response::json($data, 200);
So I'm making an API that produces a json response instead of doing View::make('foo', compact('bar')).
With blade templating
My controller simply returns the Eloquent model for Users:
public function index()
{
return User::all();
}
protected function getFooAttribute()
{
return 'bar';
}
And my view will be able to use it, along with the foo attribute (which isn't a field in the user's table).
#foreach($users as $user)
<p>{{$user->name}} {{$user->foo}}</p>
#endforeach
With Angular JS + json response
However, now that I'm not using blade but rather grabbing the json and displaying it with Angular JS I'm not able to do this:
<p ng-repeat="user in users">{{user.name}} {{user.foo}}</p>
Is there a way to cleanly pack the json response such that I have:
[{"name": "John", "foo": "bar"}, ...]
Warning: I've never built an API before and I've only started programming/web dev last December. This is my attempt:
public function index()
{
$response = User::all();
foreach($response as $r)
{
$r->foo = $r->foo;
}
return $response;
}
Yeah there is, example:
return Response::json([User:all()], 200);
Usually you want more than that though..
return Response::json([
'error' => false,
'data' => User:all()
], 200);
200 is the HTTP Status Code.
To include the attributes you need to specify these attributes to automatically append onto the response in your model.
protected $appends = array('foo');
public function getFooAttribute()
{
return 'bar';
}