Route binding with composite key - php

I am trying to bind a model that has composite key. Take a look, at first place I define my route:
Route::get('laptop/{company}/{model}', 'TestController#test');
Now, I define as I want to be resolved:
$router->bind('laptop', function ($company, $model) {
$laptop = ... select laptop where company=$company and ...;
return $laptop;
});
Now, I see how I am injecting the class in order to get the laptop in the controller: function into to test the resolution:
function test(Laptop $laptop){
return 'ok';
}
However, I am receiving the following error:
BindingResolutionException in Container.php line 839:
I assume that the error is caused by $router->bind('laptop' because it should matches a unique placeholder in the url ("company" or "model"). In my case I get lost because I need to matches both at the same time.
Note: I am not using db/eloquent layer. This problem is focused in the way on how to resolve route binding with multiples keys representing an unique object.
I am not sure if is it possible or if am I missing something. Thank you in advance for any suggestion.

Laravel does not support composite key in eloquent query.
You need to use query builder method of laravel to match against both values. ie: DB::select()->where()->where()->get();
Just put select and where conditions in above.

If you bind $router->bind('laptop', ...); then your route parameter should be Route::get('{laptop}', ...);. There is two possibility to query a laptop by model and company as you expected.
The safest way is query laptop on your controller:
Route::get('laptop/{company}/{model}', 'TestController#test');
In you TestController.php
function test(Laptop $laptop, $company, $model){
return $laptop->whereCompany($company)->whereModel($model)->first();
}
Another solution is allow slashes on your route parameter:
Route::get('laptop/{laptop}', 'TestController#test')->where('laptop', , '(.*)?');
and your binding function could be:
$router->bind('laptop', function ($laptop) {
$laptop = explode('/', $laptop);
$company = current($laptop);
$model = end($laptop);
if ((count($laptop) === 2) && ($result = App\Laptop::whereCompany($company)->whereModel($model)->first()) {
return $result;
}
return abort(404);
}

Related

Method Illuminate\Support\Collection::find does not exist

Edit function:
public function editCheck($id, LanguagesRequest $request)
{
try{
$language = language::select()->find($id);
$language::update($request->except('_token'));
return redirect()->route('admin.languages')->with(['sucess' => 'edit done by sucsses']);
} catch(Exception $ex) {
return redirect()->route('admin.addlanguages');
}
}
and model or select function
public function scopeselect()
{
return DB::table('languages')->select('id', 'name', 'abbr', 'direction', 'locale', 'active')->get();
}
This code is very inefficient, you're selecting every record in the table, then filtering it to find your ID. This will be slow, and is entirely unnecessary. Neither are you using any of the Laravel features specifically designed to make this kind of thing easy.
Assuming you have a model named Language, if you use route model binding, thing are much simpler:
Make sure your route uses the word language as the placeholder, eg maybe your route for this method looks like:
Route::post('/languages/check/{language}', 'LanguagesController#editCheck');
Type hint the language as a parameter in the method:
public function editCheck(Language $language, LanguagesRequest $request) {
Done - $language is now the single model you were afer, you can use it without any selecting, filtering, finding - Laravel has done it all for you.
public function editCheck(Language $language, LanguagesRequest $request) {
// $language is now your model, ready to work with
$language::update($request->except('_token'));
// ... etc
If you can't use route model binding, or don't want to, you can still make this much simpler and more efficient. Again assuming you have a Language model:
public function editCheck($id, LanguagesRequest $request) {
$language = Language::find($id);
$language::update($request->except('_token'));
// ... etc
Delete the scopeselect() method, you should never be selecting every record in your table. Additionally the word select is surely a reserved word, trying to use a function named that is bound to cause problems.
scopeselect() is returning a Collection, which you're then trying to filter with ->find() which is a method on QueryBuilders.
You can instead filter with ->filter() or ->first() as suggested in this answer
$language = language::select()->first(function($item) use ($id) {
return $item->id == $id;
});
That being said, you should really find a different way to do all of this entirely. You should be using $id with Eloquent to get the object you're after in the first instance.

Call a controller with a parameter?

Let's imagine I have two Models:
A list of users User
A list of marbles Marble which belongs to one User
I would like to fetch all the existing marbles with api/marbles and only my marbles with api/user/marbles. The idea is to avoid a route named like api/marbles?owned=true
In my API routes I have this:
Route::get('marbles', 'MarbleController#index');
Route::get('user/marbles', 'MarbleController#index(true)');
Then in my MarbleController:
class MarbleControllerextends Controller
{
function index($owned = false) {
return $owned ? Marble::where('user_id', Auth::id())->get() : Marble::all();
}
}
Unfortunately the MarbleController#index(true) doesn't really work because (true) will not be accepted by Laravel not populate the optional $owned variable.
Is there a way to avoid defining a new method such as Route::get('user/marbles', 'MarbleController#owned');
function owned() {
return $this->index(true);
}
Route::get('marbles/{me?}', 'MarbleController#index'); will work fine.
Here me is an optional parameter. If you omit it, it will take false otherwise true as it's value.

Laravel array key exists in get method

I am trying to fetch the the id and the name of the categories which is related to my services. A service has many categories and a categories belongs to a services. However when I try to get the id and the name as an array to return it gives me this error.
array_key_exists(): The first argument should be either a string or an
integer.
Here is my method or function.
public function getCategories($idService)
{
$service = Service::findOrFail($idService);
return $service->categories->get(['id','name']);;
}
and here is the defined route.
Route::get('service/{service}/categories', 'ServiceController#getCategories');
I tried to look and browse for it but can't find any solution at all.
Use pluck() method instead
return $service->categories->pluck('id','name');
The name of the parameter has to be equal to the wildcard and you need to use pluck() as mentioned in another comment, in your case:
public function getCategories($service)
{
$service = Service::findOrFail($service);
return $service->categories->pluck(['id','name']);
}
If service is a model you can also use eloquent:
public function getCategories(Service $service)
{
return $service->categories->pluck(['id','name']);
}
i guess it related to with eager loading..need to use eager loading to fetch the relationship.. then use laravel collection if you want to filter more
public function getCategories($idService)
{
return Service::with(['categories' => function ($query) {
$query->select('id', 'name');
}])->findOrFail($idService);
}

Dynamic model filter in Laravel's Eloquent

I'm looking for a way to make a dynamic & global model filter in Laravel.
I'm imagining a function like the following in my User.php model:
public function filter() {
return ($someVariable === true);
}
Whenever I do a query using Eloquent's query builder, I only want users to show up in the collection when the filter above returns true. I would have thought a feature like that existed, but a quick look at the documentation suggests otherwise. Or did I miss it?
I believe what you're looking for is Query Scopes.
They are methods that may be defined in a global or local context, that mutate the current query for a given model.
https://laravel.com/docs/5.5/eloquent#query-scopes
For example:
Lets say I have a database table called "Teams" and it has a column on it called "Wins." If I wanted to retrieve all Teams that had a number of Wins between Aand B I could write the following Local scope method on the teams model:
public function scopeWinsBetween($query, int $min, int $max)
{
return $query->whereBetween('wins', $min, $max);
}
And it could be invoked as such:
$teams = Teams::winsBetween(50, 100)->get();
I think you could use Collection macro but you will need to suffix all your eloquent get(); to get()->userDynamicFilter();
Collection::macro('userDynamicFilter', function () {
//$expected = ...
return $this->filter(function ($value) use($expected) {
return $value == $expected;
});
});
Thanks. For now I've simply added a post filter option to the models using the following code:
// Apply a post filter on the model collection
$data = $data->filter(function($modelObject) {
return (method_exists($modelObject, 'postFilter')) ? $modelObject->postFilter($modelObject) : true;
});
in Illuminate/Database/Eloquent/Builder.php's get() function, after creating the collection. This allows me to add a function postFilter($model) into my model which returns either true or false.
Probably not the cleanest solution but a working one for now.

Laravel's Eloquent table 'inheritence' with parents

I have a Laravel model acl_groups that has a JSON column inherits. What should I do, the "laravel way" to query the inherited groups when checking if a group can do something? The rights are stored in another JSON column, allow/deny so I can just do a in_array to check a single group if they have access.
On your model you can set a getter
public function getInheritsAttribute($v)
{
return $v ? json_decode($v, true) : [];
}
OR
if you dont want a getter you can try a pseudo getter
public function getPseudoAttribute()
{
return $this->inherits ? json_decode($this->inherits, true) : [];
}
Kind of maybe did mistake on second one.
And on other model the same thing
so when you call $item->inherits = you will get an array
First you may try to prepare the array like removing same keys or values
and after just check
if (array_key_exists('thing_to_check', $item->inherits)) {
return true;
}
This is not a working code, it is just an idea how you can do you.
Take a look at Cartalyst Sentinel how they check the permissions for groups and users.

Categories