Yii2 model relations not returned in json - php

Am fetching data with javascript to a yii2 api which i would like to also return model relations.
I have the following
In my user class i have
class User{
//relationship
public function getAuthOptions(){
return $this->hasMany(UserAuthOption::className(),["user_id"=>"id"]);
}
}
Am fetching the data as follows
$users = User::find()->with(['authOptions'])->all();
return $users.
The above returns an array of objects which doesnt contain the authOptions.
I understand that you can access the relationship data via
$users[0]->authOptions
But is there a way the relationship data can be returned on the $users query for javascript api requests which cannot access the $users[0]->authOptions
Currently am able to achieve this by adding a custom field like
class User{
public function fields()
{
$fields = parent::fields();
$fields["authOptions"]=function ($model){
return $model->authOptions;
};
return $fields;
}
public function getAuthOptions(){
return $this->hasMany(UserAuthOption::className(),["user_id"=>"id"]);
}
}
But the above is not optimal because it returns authOptions in all requests but i would like to controll which requests require authOptions to be returned.

class User extends ActiveRecord
{
public function extraFields()
{
return [
'authOptions',
];
}
public function getAuthOptions() {
return $this->hasMany(UserAuthOption::class, ['user_id' => 'id']);
}
}
After that you can use expand param when you need in your API query like this:
/api/controller/action?expand=authOptions
->with(['authOptions']) is not necessary in REST Controller.

Related

How to include an Eloquent relationship into JSON conversion result?

I want to return JSON data to my Angular front-end with Laravel 5 but I am stuck with this problem : I have an Alert eloquent model which has a relationship with AlertFrequency model:
class Alert extends Model
{
public function alertFrequency()
{
return $this->belongsTo('App\Models\AlertFrequency');
}
}
but when I try to return the alerts listing as a JSON...
$alerts = Auth::user()->alerts->toJson();
return $alerts;
here is what I get :
[{"id":9,"title":"fqdsgsq","place":"gdqsgq","user_id":1,"alert_frequency_id":3,"job_naming_id":3,"created_at":"2016-07-16 14:09:41","updated_at":"2016-07-16 14:09:41"}]
So I do have the "alert_frequency_id" column value but not the actual AlertFrequency object
The only workaround I found to be working so far is this :
$alerts = Auth::user()->alerts;
foreach ($alerts as $alert) {
$alert->jobNaming = $alert->jobNaming;
$alert->alertFrequency = $alert->alertFrequency;
}
return $alerts;
which is very ugly...
You need to manually load the relationship. This is called lazy eager loading a relationship.
$alerts = Auth::user()->alerts->load('alertFrequency')->toJson();
return $alerts;
When you do Auth::user()->alerts->toJson() you just fetched the data from the Alert model, but you want data from another model (another table) then you need to tell that using the load method.
Bonus
If your code is in a controller method or a router closure instead of calling toJson() you can just return the collection and Laravel will do that for you. Example:
public function controllerMethod() {
return Auth::user()->alerts->load('alertFrequency');
}
Happy coding!

How to change default Yii 2 Rest API GET response result

According to Yii 2 Rest APi documentation, I have a CountriesCountry that extends \yii\rest\ActiveController and a corresponding Countries model. This is the code for my Controller class.
<?php
namespace app\controllers;
class CountriesController extends \yii\rest\ActiveController{
public $modelClass = 'app\models\Countries';
public function actionIndex(){
}
public function actionView(){
}
public function actionCreate(){
}
public function actionUpdate(){
}
public function actionDelete(){
}
public function actionOptions(){
}
}
When I send a get request, it returns all the countries in my database.
My Question
is it possible to return my own result from action methods. Like in the actionIndex(), I will like to limit the result to 20 records. I did something like this but it is not working.
public function actionIndex(){
$model = Countries::find()->limit(20);
print_r($model);
}
I know that I can get all the countries from by database and loop through it and obtain only 20 results but I want to just query for 20 records from database.
Your class CountriesController that extends from \yii\rest\ActiveController automatically supports GET, PUT, POST calls etc. No need for actionIndex(), actionCreate(), etc if you just want regular REST functionality. Read about it in the Yii2 guide.
To limit the results you could just set another page size in your controllers afterAction-method. Add this to your controller. (I believe that 20 records is default of Pagination class, so if that is what you want you don't need this code at all. Just use the default functionality of yii/rest/ActiveController.)
public function afterAction($action, $result) {
if (isset($result->pagination) && ($result->pagination !== false)) {
$result->pagination->setPageSize(100);
}
return parent::afterAction($action, $result);
}
Suppose your api link is:
http://localhost/yii2-rest/api/country/?limit=15&order=id
Controller:
public function actionIndex(){
$model = Countries::find()
->orderBy($_GET['order'])
->limit($_GET['limit'])
->all();
return $model;
}
Take care about security!
You can get query string this way:
$limit = Yii::app()->getRequest()->getQuery('limit');
Straightway
//SELECT * FROM countries LIMIT 20
$countries= Countries::find()->limit(20)->all();

Laravel 5 requests: Authorizing and then parsing object to controller

I am not sure if I am using this correctly, but I am utilising the requests in Laravel 5, to check if the user is logged in and if he is the owner of an object. To do this I need to get the actual object in the request class, but then I need to get the same object in the controller?
So instead of fetching it twice, I thought, why not just set the object as a variable on the request class, making it accessible to the controller?
It works, but I feel dirty? Is there a more appropriate way to handle this?
Ex.
Request Class
class DeleteCommentRequest extends Request {
var $comment = null;
public function authorize() {
$this->comment = comment::find(Input::get('comment_id'));
$user = Auth::user();
if($this->comment->user == $user)
return true;
return false;
}
public function rules() {
return [
'comment_id' => 'required|exists:recipes_comments,id'
];
}
}
Ex. Controller:
public function postDeleteComment(DeleteCommentRequest $request) {
$comment = $request->comment;
$comment->delete();
return $comment;
}
So what is my question? How do I best handle having to use the object twice when using the new Laravel 5 requests? Am I possibly overextending the functionality of the application? Is it ok to store the object in the application class so I can reach it later in my controller?
I would require ownership on the query itself and then check if the collection is empty.
class DeleteCommentRequest extends Request {
var $comment = null;
public function authorize() {
$this->comment = comment::where('id',Input::get('comment_id'))->where('user_id',Auth::id())->first();
if($this->comment->is_empty())
return false;
return true;
}
public function rules() {
return [
'comment_id' => 'required|exists:recipes_comments,id'
];
}
}
Since you're wanting to use the Model in two different places, but only query it once I would recommenced you use route-model binding.
In your RouteServiceProvider class (or any relevant provider) you'll want to bind the comment query from inside the boot method. The first parameter of bind() will be value that matches the wildcard in your route.
public function boot()
{
app()->router->bind( 'comment_id', function ($comment_id) {
return comment::where('id',$comment_id)->where('user_id',Auth::id())->first();
} );
}
Once that's set up you can access the Model from your DeleteCommentRequest like so
$this->comment_id
Note: The variable is Comment_id because that's what matches your route, but it will contain the actual model.
From your controller you just inject it like so
public function postDeleteComment(Comment $comment, DeleteCommentRequest $request) {
$comment->delete();
return $comment;
}

Laravel get related models of related models

I have a RepairRequest model, which is associated with a Vehicle.
class RepairRequest extends \Eloquent {
public function vehicle() {
return $this->belongsTo('Vehicle');
}
}
class Vehicle extends \Eloquent {
public function requests() {
return $this->hasMany('RepairRequest');
}
}
I would like to get all RepairRequests for the vehicle associated with a given RepairRequest, so I do
return RepairRequests::find($id)->vehicle->requests;
This works fine.
However, RepairRequests have RepairItems:
// RepairRequest class
public function repairItems() {
return $this->hasMany('RepairItem', 'request_id');
}
// RepairItem class
public function request() {
return $this->belongsTo('RepairRequest', 'request_id');
}
which I would like to return too, so I do
return RepairRequests::find($id)->vehicle->requests->with('repairItems');
but I get the following exception:
Call to undefined method Illuminate\Database\Eloquent\Collection::with()
How can I write this so that the returned json includes the RepairItems in the RepairRequest json?
Load related models using load method on the Collection:
return RepairRequests::find($id)->vehicle->requests->load('repairItems');
which is basically the same as:
$repairRequest = RepairRequests::with('vehicle.requests.repairItems')->find($id);
return $repairRequest->vehicle->requests;
I'd suggest eager loading everything.
return RepairRequests::with('vehicle.requests.repaireItems')->find($id);

Laravel: Pass Parameter to Relationship Function?

Is it possible to pass, somehow, a parameter to a relationship function?
I have currently the following:
public function achievements()
{
return $this->belongsToMany('Achievable', 'user_achievements')->withPivot('value', 'unlocked_at')->orderBy('pivot_unlocked_at', 'desc');
}
The problem is that, in some cases, it does not fetch the unlocked_at column and it returns an error.
I have tried to do something like:
public function achievements($orderBy = true)
{
$result = $this->belongsToMany (...)
if($orderBy) return $result->orderBy(...)
return $result;
}
And call it as:
$member->achievements(false)->(...)
But this does not work. Is there a way to pass parameters into that function or any way to check if the pivot_unlocked_at is being used?
Well what I've did was just adding new attribute to my model and then add the my condition to that attirbute,simply did this.
Class Foo extends Eloquent {
protected $strSlug;
public function Relations(){
return $this->belongsTo('Relation','relation_id')->whereSlug($this->strSlug);
}
}
Class FooController extends BaseController {
private $objFoo;
public function __construct(Foo $foo){
$this->objFoo = $foo
}
public function getPage($strSlug){
$this->objFoo->strSlug = $strSlug;
$arrData = Foo::with('Relations')->get();
//some other stuff,page render,etc....
}
}
You can simply create a scope and then when necessary add it to a builder instance.
Example:
User.php
public function achievements()
{
return $this->hasMany(Achievement::class);
}
Achievement.php
public function scopeOrdered(Builder $builder)
{
return $builder->orderBy(conditions);
}
then when using:
//returns unordered collection
$user->achievements()->get();
//returns ordered collection
$user->achievements()->ordered()->get();
You can read more about scopes at Eloquent documentation.
You can do more simple, and secure:
When you call the relation function with the parentesis Laravel will return just the query, you will need to add the get() or first() to retrieve the results
public function achievements($orderBy = true)
{
if($orderBy)
$this->belongsToMany(...)->orderBy(...)->get();
else
return $this->belongsToMany(...)->get();
}
And then you can call it like:
$member->achievements(false);
Works for the latest version of Laravel.
Had to solve this another was as on Laravel 5.3 none of the other solutions worked for me. Here goes:
Instantiate a model:
$foo = new Foo();
Set the new attribute
$foo->setAttribute('orderBy',true);
Then use the setModel method when querying the data
Foo::setModel($foo)->where(...)
This will all you to access the attribute from the relations method
public function achievements()
{
if($this->orderBy)
$this->belongsToMany(...)->orderBy(...)->get();
else
return $this->belongsToMany(...)->get();
}

Categories