Using accessors and mutators in Laravel json responses - php

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';
}

Related

No meta value from collection object

class SongsCollection extends ResourceCollection
{
public function toArray($request)
{
return [
'data' => SongResource::collection($this->collection),
'meta' => ['song_count' => $this->collection->count()],
];
}
}
in the controller
$data = new SongsCollection(Song::all());
dd($data);
`
it only display the image below, but without the meta array contain the song_count?
How to get the meta->song_count value ?
Laravel doc says:
Every resource class defines a toArray method which returns the array of attributes that should be converted to JSON when the resource is returned as a response from a route or controller method.
So, dd($data) will just dump the resource object which has the toArray method. If you want to get the meta field, you must call $data->toJson() method. If you just return $data in your api endpoint, laravel itself will call toJson method internally as stated in the above doc.
In the blade template, the laravel gives a song collection, which can not be retrive the meta from this collection, Below is how I get the meta data:
$datas = json_encode($datas,true);
//dd($data);
$datas = json_decode($datas);
// dd($datas);
foreach($datas->meta as $link){
echo $link;
}
Hope this helps.

Creating a record to laravel databse with vue.js doesn't work

I'm trying to create a new record to my database using Vue.js in laravel, my API, data is okay. But I get that strange error when I click create on my site.
Argument 1 passed to Illuminate\Database\Grammar::parameterize() must be of the type array, string given, called in C:\Users\PC\Desktop\Productivity\gitdemo\students\vendor\laravel\framework\src\Illuminate\Database\Query\Grammars\Grammar.php on line 884
I also have delete function that works perfectly, but this doesn't work... Form inputs have v-model, that seems alright, but it still doesn't work.
My create method on vue.js
addStudent() {
axios.post(`api/students/create`, {data:this.students});
}
Getting records in vue.js
data() {
return{
students: [],
}
},
created() {
axios.get(`api/students`).then(response =>{
this.students = response.data.data;
})
},
Controller
public function store(Request $request)
{
Student::create($request->all());
return view('welcome');
}
Route (in api.php)
Route::post('students/create', 'StudentController#store');
Student Model
use SoftDeletes;
protected $dates = ['birth_date'];
protected $guarded = [];
Students array in data has all of the v-model namings
You should not directly pass $request->all() to create method. Based on what you send from front end to back end, do this:
$r = $request->all();
$students = $r['data'];
Student:create($students);

Yii2 rest API get post with user

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

Filtering data in laravel and handling the views

I have a tv show netflix-esque project I'm building where I have a Shows page which I want to filter on format. Each show contains episodes which can have a tv, dvd and bd format.
Currently I'm filtering using separate routes and controllers which extend the base ShowsController.
Route::get('shows/view/{type}', ['as' => 'shows.viewtype', 'uses' => 'ShowsController#viewType',]);
Route::get('shows/bluray',['as' => 'shows.bluray','uses' => 'ShowsBlurayController#index']);
Route::get('shows/dvd',['as' => 'shows.dvd','uses' => 'ShowsDVDController#index']);
Route::get('shows/tv',['as' => 'shows.tv','uses' => 'ShowsTVController#index']);
Example of one of the format controllers
class ShowsBlurayController extends ShowsController
{
public function index()
{
// Set user state for browsing bluray
Session::push('user.showtype', 'bluray');
$shows = $this->show->getBlurayPaginated(16);
return $this->getIndexView(compact('shows'));
}
}
I use the getIndexView() method (in the ShowsController) to determine one of 2 available views: poster and list.
public function getIndexView($shows)
{
$viewType = get_session_or_cookie('show_viewtype', 'list');
if ($viewType == 'posters') {
return View::make('shows.index', $shows)
->nest('showsView', 'shows.partials.posters', $shows);
} else {
return View::make('shows.index', $shows)
->nest('showsView', 'shows.partials.list', $shows);
}
}
The shows are filtered based on the episodes:
public function getBlurayPaginated($perPage)
{
return $this->getByFormat('BD')->with('tagged')->paginate($perPage);
}
private function getByFormat($format)
{
return $this->show->whereHas('episodes', function ($q) use ($format) {
$q->whereHas('format', function ($q) use ($format) {
$q->where('format', '=', $format);
});
});
}
The problem is that I want to do this in a clean way. When a user selects a format, that filter will be applied. Currently all of this is kind of scattered across controllers and doesn't quite make sense.
I also thought of doing something like this in the routes.php:
Route::get('shows/format/{format}',['as' => 'shows.format','uses' => 'ShowsController#index']);
And then handle the filtering in the index, but that also seems a weird place to do that.
This approach does work, but I don't want to screw myself later on with it. I'm planning a simple search which should take the filter into account.
In other words, how can I organize the code in such a way that getting data from the database will take the filter into account which has been set? (Session states maybe?)
Route::get('shows/format/{format}',[
'as' => 'shows.format',
'uses' => 'ShowsController#index'
]);
I think you're on the right track here. I would go so far as to produce a factory and inject it into the controller. The purpose of this factory is to construct a formatter that will supply your view with the correct data:
// ShowController
public function __construct(ShowFormatFactory $factory, ShowRepository $shows)
{
$this->factory = $factory;
// NB: using a repository here just for illustrative purposes.
$this->shows = $shows;
}
public function index($format = null)
{
$formatter = $this->factory->make($format);
return View::make('shows.index', [
'formatter' => $formatter,
'shows' => $this->shows->all(),
]);
}
// ShowFormatFactory
class ShowFormatFactory
{
public function make($format)
{
switch($format) {
case 'blueray':
return new BluerayFormat(); break;
case 'dvd': /* Fallthrough for default option */
default:
return new BluerayFormat(); break;
}
}
}
// ShowFormatInterface
interface ShowFormatInterface
{
public function format(Show $show);
}
// BluerayFormat
class BluerayFormat implements ShowFormatInterface
{
public function format(Show $show)
{
return $show->blueray_format;
}
}
Then in your view, since you are guaranteed to have an object that will provide you the format requested for a given show, just call it:
#foreach($shows as $show)
<div class="show">
Chosen Format: {{ $formatter->format($show) }}
</div>
#endforeach
This solution is testable and extensible will allow you to add other formats later on. If you do, you would need to add a discrete case statement in the factory for each different format, as well as write a rather slim ~5-7 line class to support the new format.

Laravel presenters for JSON responses

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);

Categories