How to bind model to query string parameter in Laravel 4? - php

I understand that you can bind Models to route parameters in Laravel, but is there a way they can be bound to optional query string parameters.
For example, given a URL of:
http://myapi.example.com/products?category_id=345
That uses a route of:
Route::resource('products', 'ProductController');
Is there a way to bind our optional query string parameter category_id to our Category model, so that it can be automatically injected into the ProductController?

At this moment, I don't think that is possible with a resource route because it automatically maps some RESTful routes to a given resource (as per the docs). If you want a route with optional parameters, you will have to use some of the other options for writing routes in Laravel and make sure you place it before declaring the resource controller.

This is possible, but not as your question asks.
Is there a way to bind our optional query string parameter category_id
to our Category model, so that it can be automatically injected into
the ProductController?
You bind the query string parameter to the product model, not the category model.
Below is a quick and dirty method of sending filtered data to the view based on query string input. This filters the category relation.
There may be syntax errors as I just quickly knocked an answer out - but the concept works.
ProductController
class ProductController extends BaseController
{
protected $productModel;
function __construct(Product $productModel)
{
$this->$productModel = $productModel;
}
public function index()
{
$products = $this->productModel
->join('categories', 'categories.id', '=', 'products.category_id');
->select(// select your columns)
$filterCategory = Input::get('category_id');
if ($filterCategory)
{
->where('category_id', '=', $filterCategory)
}
$data = $products->get();
return View::make( 'shop.product.index')->with($data);
}
}
A better solution would be to abstract this code away from the controller and override the newQuery method of the model.
It was in fact Glad to Help's answer in one of my similar questions who introduced me to the newQuery method. Laravel 4: Select row if a relation exists by querying relation

Related

Laravel Eloquent how pass variable to with relationship function nested

I wish to pass a variable to nested relationship with eloquent.
I read this Laravel Eloquent pass variable to with relationship function but in that answer it does not look at a nested relationship.
I've this scenario:
Category::where("company", $company)->whereNull("parent_ecommerce_id")->with(['children']);
My model
public function children() {
return $this->hasMany(Category::class,'parent_ecommerce_id','category_ecommerce_id')->with("children");
}
I can modify my first call with this as suggest in link posted
Category::where("company", $company)->whereNull("parent_ecommerce_id")->with(['children' => function($query) use ($company) {
$query->where("company", $company);
}]);
and it works in first call, but in nested callback, I haven't more the filter with "company". How can I filter the company in all nested relationship? I thinked passing variable to "children($var)", but I don't find solution to pass that in "with" statement.
If I get you correctly, I believe it should be more like this.
If what you are trying to achieve is to fetch all Categories that with children, where the companies have been filtered.
Category::where("company", $company)->whereNull("parent_ecommerce_id")->whereHas('children', function($query) use ($company) {
$query->where("company", $company);
}])->with(['children']);
I think this way is not right, if there is necessary of a second variable filter, we must use another way to get all category tree as is explained here https://www.youtube.com/watch?v=hFxR6D2kH44

Laravel 8 - How to set a different relation name for scoping in Nested Resource Route

Routes:
I have a nested resource route. I use this to declare it:
Route::resource('orders.comments', \App\Http\Controllers\Backend\OrderCommentController::class)
->parameters([
'comments' => 'orderComment:id'
])
->except(['show']);
Models & Relations:
I have two models, Order and OrderComment.
Order model:
public function comments()
{
return $this->hasMany(OrderComment::class);
}
OrderComment model:
public function order()
{
return $this->belongsTo(Order::class);
}
OrderComment Controller edit method signature
public function edit(Order $order, OrderComment $orderComment)
The actual problem
Whenever I want to edit an order comment, by visiting /orders/1/comments/1/edit, I get the error:
Call to undefined method App\Models\Order::orderComments()
I assume this is based on the parameter I set in the resource. But I need the parameter to be orderComment and not comment, due to the $orderComment argument in OrderCommentController#edit. This is mainly due to the name convention.
Possible, ruled out solutions i tried/considered:
Renaming the OrderComment model to Comment. This is not possible because there are other Comment models, with different columns, so it cannot be polymorphic too.
Renaming the comments relation to orderComments. This is a solution, but not a preferred one. This is because $order->comments looks more elegant than $order->orderComments, etc
Conclusion/question
So is it possible to 'tell' Laravel it should look for a relation called comments instead of orderComments?

nested resources in laravel

I´m building my API (makes and models) and I want to have nested resources (not sure if this is correct Restfully speaking)
/makes/ferrari/models
/makes/ferrari/models/f40
I defined the following route
Route::resource('makes.models', 'ModelsController');
and the ModelsController.php
/**
* Display a listing of the resource.
*
* #return \Illuminate\Http\Response
*/
public function index()
{
$data = Models::all();
return response()->json($data);
}
/**
* Display the specified resource.
*
* #param int $id
* #return \Illuminate\Http\Response
*/
public function show($id)
{
$data = Models::find($id);
return response()->json($data);
}
and the models model (yeah I need to change the name)
class Models extends Model
{
public function make()
{
return $this->belongsTo('App\Make');
}
}
my problem is that even if the route works it returns all models in the db (not only ferraris) where should I define that relationship? is not automatic?
I have 2 tables makes (id, name), models (id, name, make_id)
thank you!
The resource route will define the following routes:
Method Path Action
GET /makes/{make}/models index
GET /makes/{make}/models/create create
POST /makes/{make}/models store
GET /makes/{make}/models/{id} show
GET /makes/{make}/models/{id}/edit edit
PUT /makes/{make}/models/{id} update
DELETE /makes/{make}/models/{id} destroy
Your request /makes/ferrari/models will not match any of those routes (as your show parameter only takes one parameter). You may request for /makes/models/1 to call show, but you are practically missing the route for this, as the nested route does not provide it.
If you say that you always get all items, you are very likely hitting the index action instead of show.
If you want to query your models with /makes/ferrari/models/f40, you would need a route like this:
Route::get('/makes/{make}/model/{model}', 'ModelsController#show');
Which is already part of the resource route created for you.
Now, in your show controller, use the make and model parameters to find the correct dataset:
public function show($make, $model)
{
$data = Model::with('makes')
->whereName($model)
->whereHas('makes', function ($query) use ($make) {
$query->where('name', '=', $make);
})->get();
return response()->json($data);
}
Laravel doesn't automatically do that for you.
Update: Route model binding
You might want to check out https://laravel.com/docs/5.3/routing#route-model-binding for a more sophisticated way of doing this. You can set your route key name in both of your models overwriting the getRouteKeyName() method and returning 'name' in this case, telling Laravel to use the name column instead of the id.
You can also bind parameters in your routes specifically to a custom resolution logic by doing something like
$router->bind('model', function ($value) {
return Model::where('name', $value)->first();
});
and then every time you use {model} in your routes, it will use the name instead of the id.
Use slugs
However, be advised that you have to make absolutely sure that the names stored in the database for model and make are sluggified so that they are suited for use in URLs. If necessary, you may possibly do that in your bind as shown above, returning
return str_slug(Model::where('name', $value)->first());
This is untested, however, so it might or might not work.
Hope that helps :-)
When using nested resources, all your controller actions will receive an additional first parameter (the parent resource identifier). So you need to update your controller actions accordingly:
public index($make) {
$make = Make::with('models')->where('name', $make)->firstOrFail();
return view('models.index', compact('make'));
}
public show($make, $model) {
$make = Make::with('models')
->where('name', $make)
->firstOrFail();
$model = $make->models()
->where('name', $make)
->firstOrFail();
return view('models.show', compact('make', 'model'));
}
It should be the same with your other controller actions.
Note that I made assumptions regarding the structure of your database.

Laravel relationship manytomany paginate issue

I have 3 tables / models
User (has an id)
Articles (has an id )
UserArticles (has an id, article_id and user_id)
I am a little confused on how I would set up the relationship so that I will be able to get all articles connected to a user so I can set up a call like so in my controller:
$articles = User::find(Auth::user()->id)->articles->paginate(20);
I figured this was a manytomany relationship so I am playing around with this code inside the User model:
public function articles()
{
return $this->belongsToMany('App\Article', 'user_saved_articles', 'user_id', 'article_id');
}
Now this works, as long as I don't call paginate() on the controller function I'd like to use above. Here is where my real issue lies now, so it works with
$articles = User::find(Auth::user()->id)->articles;
but with this:
$articles = User::find(Auth::user()->id)->articles->paginate(20);
it comes up with the error:
FatalErrorException in UserController.php line 217:
Call to undefined method Illuminate\Database\Eloquent\Collection::paginate()
I can't figure out why I can't paginate on this, as I can with all my other queries.
If you call an eloquent relation as an attribute User::find(Auth::user()->id)->articles it will automatically execute the SQL, and return a Collection. The error is telling you that you have a Collection, which you can't call paginate() on.
If you want to reference the relationship to add more statements you need to call it as a function User::find(Auth::user()->id)->articles(). This will return a QueryBuilder instance that you can add statements to, and/or paginate.
So this should work:
$articles = User::find(Auth::user()->id)->articles()->paginate(20);

Laravel, How to use where conditions for relation's column?

I'm using Laravel and having a small problem with Eloquent ORM.. I can get this working simply with SQL query using a JOIN but I can't seem to get it working with Eloquent!
This is what I want, I have two tabels. one is 'Restaurants' and other is 'Restaurant_Facilities'.
The tables are simple.. and One-To-One relations. like there is a restaurant table with id, name, slug, etc and another table called restaurant_facilities with id, restaurant_id, wifi, parking, etc
Now what I want to do is.. load all restaurants which have wifi = 1 or wifi = 0..
How can i do that with Eloquent ? I have tried eager loading, pivot tables, with(), collections() and nothing seems to work!
The same problem I have for a Many-To-Many relation for cuisines!
I have the same restaurant table and a cuisine table and a restaurant_cuisine_connection table..
but how do I load all restaurants inside a specific cuisine using it's ID ?
This works.
Cuisine::find(6)->restaurants()->get();
but I wanna load this from Restaurant:: model not from cuisines.. because I have many conditions chained together.. its for a search and filtering / browse page.
Any ideas or ways ? I've been struggling with this for 3 days and still no answer.
Example Models :
class Restaurant extends Eloquent {
protected $table = 'restaurants';
public function facilities() {
return $this->hasOne('Facilities');
}
}
class Facilities extends Eloquent {
protected $table = 'restaurants_facilities';
public function restaurant() {
return $this->belongsTo('Restaurant');
}
}
PS :
This seems to be working.. but this is not Eloquent way right ?
Restaurant::leftJoin(
'cuisine_restaurant',
'cuisine_restaurant.restaurant_id',
'=', 'restaurants.id'
)
->where('cuisine_id', 16)
->get();
Also what is the best method to find a count of restaurants which have specific column value without another query ? like.. i have to find the total of restaurants which have parking = 1 and wifi = 1 ?
Please help on this.
Thank you.
I don't see anything wrong with doing the left join here, if you have to load from the Restaurant model. I might abstract it away to a method on my Restaurant model, like so:
class Restaurant extends Eloquent {
protected $table = 'restaurants'; // will be default in latest L4 beta
public function facility()
{
return $this->hasOne('Facility');
}
// Or, better, make public, and inject instance to controller.
public static function withWifi()
{
return static::leftJoin(
'restaurant_facilities',
'restaurants.id', '=', 'restaurant_facilities.restaurant_id'
)->where('wifi', '=', 1);
}
}
And then, from your routes:
Route::get('/', function()
{
return Restaurant::withWifi()->get();
});
On the go - haven't tested that code, but I think it should work. You could instead use eager loading with a constraint, but that will only specify whether the facility object is null or not. It would still return all restaurants, unless you specify a where clause.
(P.S. I'd stick with the singular form of Facility. Notice how hasOne('Facilities') doesn't read correctly?)
I stumbled across this post while trying to improve my REST API methodology when building a new sharing paradigm. You want to use Eager Loading Constraints. Let's say you have an api route where your loading a shared item and it's collection of subitems such as this:
/api/shared/{share_id}/subitem/{subitem_id}
When hitting this route with a GET request, you want to load that specific subitem. Granted you could just load that model by that id, but what if we need to validate if the user has access to that shared item in the first place? One answer recommended loading the inversed relationship, but this could lead to a confusing and muddled controller very quickly. Using constraints on the eager load is a more 'eloquent' approach. So we'd load it like this:
$shared = Shared::where('id', $share_id)
->with([ 'subitems' => function($query) use ($subitem_id) {
$query->where('subitem_id', $subitem_id)
}]);
So where only want the subitem that has that id. Now we can check if it was found or not by doing something like this:
if ($shared->subitems->isEmpty())
Since subitems is a collection (array of subitems) we return the subitem[0] with this:
return $shared->subitems[0];
Use whereHas to filter by any relationship. It won't join the relation but it will filter the current model by a related property. Also look into local scopes to help with situations like this https://laravel.com/docs/5.3/eloquent#local-scopes
Your example would be:
Restaurant::whereHas('facilities', function($query) {
return $query->where('wifi', true);
})->get();
Restaurant::whereHas('cuisines', function($query) use ($cuisineId) {
return $query->where('id', $cuisineId);
})->get();
To achieve the same thing with local scopes:
class Restaurant extends Eloquent
{
// Relations here
public function scopeHasWifi($query)
{
return $query->whereHas('facilities', function($query) {
return $query->where('wifi', true);
});
}
public function scopeHasCuisine($query, $cuisineId)
{
return $query->whereHas('cuisines', function($query) use ($cuisineId) {
return $query->where('id', $cuisineId);
});
}
}
For local scopes you DO NOT want to define them as static methods on your model as this creates a new instance of the query builder and would prevent you from chaining the methods. Using a local scope will injects and returns the current instance of the query builder so you can chain as many scopes as you want like:
Restaurant::hasWifi()->hasCuisine(6)->get();
Local Scopes are defined with the prefix scope in the method name and called without scope in the method name as in the example abover.
Another solution starring whereHas() function:
$with_wifi = function ($query) {
$query->where('wifi', 1);
};
Facilities::whereHas('restaurant', $with_wifi)
Nice and tidy.
Do you absolutely have to load it from the Restaurant model? In order to solve the problem, I usually approach it inversely.
Facilities::with('restaurant')->where('wifi' ,'=', 0)->get();
This will get all the restaurant facilities that match your conditions, and eager load the restaurant.
You can chain more conditions and count the total like this..
Facilities::with('restaurant')
->where('wifi' ,'=', 1)
->where('parking','=', 1)
->count();
This will work with cuisine as well
Cuisine::with('restaurant')->where('id','=',1)->get();
This grabs the cuisine object with the id of 1 eager loaded with all the restaurants that have this cuisine

Categories