I successfully created a global scope in Laravel and I want to query a relation model in the global scope. I have a Video model, a Category mode, and a VideoCategory pivot model and I want to access the category model using video model in the global scope, such as:
<?php
use Illuminate\Database\Eloquent\ScopeInterface;
use Illuminate\Database\Eloquent\Builder;
class DefaultScope implements ScopeInterface{
public function apply(Builder $builder)
{
$model = $builder->getModel();
$builder->whereHas('categories', function( $q ){
$q->where('language', 2);
});
}
public function remove(Builder $builder)
{
}
}
Is that a possible thing to do?
Short answer: don't do that.
How to? Use manual joins, but again, don't.
It will lead to errors, unexpected behaviour, and you will quit it as soon as you got it work.
Eloquent creates new query for a model in so many places, that using has is impossible, what you have already noticed, I suppose. Using manual joins would let you do that for given model, but would also break 90% of relation based features of Eloquent, ie. you couldn't use has or with towards this model etc.
Related
I'm currently trying to use Laravel Relationships to access my achievements Model using User model, I use this relationship code:
public function achievements()
{
return $this->hasMany('App\Models\User\Achievement');
}
I can easily make some eloquent queries, however I can't access any method that I created there, I can't access this method:
class Achievement extends Model
{
public function achievementsAvailableToClaim(): int
{
// Not an eloquent query
}
}
Using the following code:
Auth::user()->achievements()->achievementsAvailableToClaim();
I believe that I am using this Laravel function in the wrong way, because of that I tried something else without using relationship:
public function achievements()
{
return new \App\Models\User\Achievement;
}
But that would have a performance problem, because would I be creating a new class instance every time I use the achievements function inside user model?
What would be the right way of what I'm trying to do?
it's not working because your eloquent relationship is a hasMany so it return a collection. you can not call the related model function from a collection.
you can var dump it on tinker to understand more what i mean.
You can use laravel scopes.Like local scopes allow you to define common sets of constraints that you may easily re-use throughout your application.
In your case you use this like, Define scope in model:
public function scopeAchievementsAvailableToClaim()
{
return $query->where('achivement_avilable', true);
}
And you can use this like :
Auth::user()->achievements()->achievementsAvailableToClaim();
I'm new in Laravel, I want to know where is the correct place where define functions that query table from DB. In Model or in Controller?
Example:
public function insertUser($firstname, $lastname, $email) {
$user = new User();
$user->firstname = $firstname;
$user->lastname = $lastname;
$user->email = $email;
$user->save();
return $user;
}
The function above where I should declare? Models or Controllers?
Edit:
For example: I need to create a function that return male authors that live in USA and their books. I define AuthorController that use Author (Model). What's the right way to define this function? I write a function in my controller that accept gender and nation as arguments, like:
public function getAuthoursByGenderAndNation($gender, $nation) {
$authors = Author::with("books")->where("gender", "=", $gender)->where("nation", "=", $nation)->get();
return $authors;
}
Or I define a generic function that returns all authors with their books and then apply where clause on function that call this generic function? Like:
public function showAuthors(Request $request) {
$gender = $request->get("gender");
$nation = $request->get("nation");
$authors = $this->getAuthors()->where("gender", "=", $gender)->where("nation", "=", $nation)->get();
return view("authors", ["authors" => $authors]);
}
public function getAuthors() {
$authors = Author::with("books");
return $authors;
}
keep in mind that all application logics should be in controller, and all data operations should be in model. in your question insert user is a application logic, so you should place that on controller, but if you want to define how data is managed, place that method in model. For example, you want a model has ability to retrieve a collection with some condition, may be a user with female gender only so you can Access it via Modell::getFemale()
The function you mention, should be used within a controller. I would recommend that you get a grasp on how MVC works before you dive in Laravel.
Reading that may be useful to you
MVC Concept
Laravel Docs
PHP MVC Tutorial
As according to MCV recommendations.
M (model) should be fat and C (controller) should be thin.
you should write your all database transaction related code in model. Even you can create repositories for database queries.
Your controller should be thin, so you should write only logical code there, like calling model function.
Example:
UserController.php
<?php
namespace App\Http\Controllers;
use App\Http\Controllers\Controller;
use App\Http\Requests\Request;
class UserController extent Controller {
use App\User;
protected $_user;
public function __construct(User $user) {
$this->_user= $user;
}
function saveUser(Request $request) {
$user->fill($request->all());
$user->save();
// or you can directly save by $user->create($request->all());
}
}
This is how you can directly fill data to your User model with $fillable attribute defined there as
$fillable= ['name','email','password'];
If you define your model under the conventions of Eloquent you can simply use the built in Eloquent methods to insert your user as demonstrated in the documentation.
https://laravel.com/docs/5.3/eloquent#inserting-and-updating-models
In the wider scope of your question: 'where to define functions that query the DB table'.
I would suggest typically defining these on the model and looking to make use of the structures provided by Eloquent, for example defining scoped queries on your model.
The code in your controller would then call methods on your model eg.
Model::create();
It also appears you are trying to insert users. I would strongly suggest you look into using Laravel's built in Authentication structures. You'll find these very powerful.
https://laravel.com/docs/5.3/authentication
Hope this helps get you started.
I want to add where condition to my Model when with('category') is called. My relations are like this:
class Post extends Model
{
public function category()
{
return $this->belongsTo(Category::class);
}
}
Now I use this code to display post categories:
Post::where('slug', $slug)->with('category')->get();
I want to add where condition to Post Model when with('category') is called. I should display only posts.status== published if with('category') is called.
I think return $this->belongsTo(Category::class); is where i should add my where condition, but this doesn't work:
return $this->query()->where('posts.status', 'published')->getModel()->belongsTo(User::class)
How can I add where condition to all post queries if with('category') is called?
I know Laravel query scopes, but i think there is a simpler way we can use. (perhaps on $this->belongsTo(Category::class))
Relationships are implemented using additional queries. They are not part of the base query, and do not have access to modify the base query, so you cannot do this inside the relationship method.
The best way to do this is with a query scope:
Class:
class Post extends Model
{
public function scopeWithCategory($query)
{
return $query->with('category')->where('status', 'published');
}
}
Query:
$posts = Post::where('slug', $slug)->withCategory()->get();
Edit
Based on your comment, I think you've probably asked the wrong question. You may want to post another question explaining what you have setup, and what you need to do, and see if anyone has any suggestions from there.
However, to answer this specific question, I believe you should be able to do this using a global query scope. This is different than a local scope described in my original answer above.
Global query scopes are applied when get() is called on the Eloquent query builder. They have access to the query builder object, and can see the items that have been requested to be eager loaded. Due to this, you should be able to create a global query scope that checks if category is to be eager loaded, and if so, add in the status constraint.
class Post extends Model
{
/**
* The "booting" method of the model.
*
* #return void
*/
protected static function boot()
{
// make sure to call the parent method
parent::boot();
static::addGlobalScope('checkCategory', function(\Illuminate\Database\Eloquent\Builder $builder) {
// get the relationships that are to be eager loaded
$eagers = $builder->getEagerLoads();
// check if the "category" relationship is to be eager loaded.
// if so, add in the "status" constraint.
if (array_key_exists('category', $eagers)) {
$builder->where('status', 'published');
}
});
}
}
The code above shows adding in a global scope using an anonymous function. This was done for ease and clarity. I would suggest creating the actual scope class, as described in the documentation linked above.
This should work:
Post::where(['slug' => $slug, 'status' => 'published'])->with('category')->get();
you have to use withPivot() method .
class Post extends Model
{
public function category()
{
return $this->belongsTo(Category::class)->withPivot('status');
}
}
please refer to my question here
I have a table full of Products. But, for each user is only allowed a group of Products to see. This is done by the table users_products_permissions. Each user has an instance of UsersPermissions table. In this table I set a range of restrictions to be applied on the user, restrictions to say each product a user is allowed to "see". This is done by a 'weak' (That's how we call in brasilian portuguese) table composed only by user_permission_id and product_id.
By the time of the requesition the requester user will be logged, of course.
The questions rests on: How to filter user's access to products based on it's restrictions?
Without checking in every requisition, manually, if the user model have access to this product. An automatic way, lets say...
In case any code is needed, just say it!
For 4.2:
Use a global scope to scope an entire model.
http://laravel.com/docs/4.2/eloquent#global-scopes
and
http://softonsofa.com/laravel-how-to-define-and-use-eloquent-global-scopes/
For 4.0:
http://www.laravel-tricks.com/tricks/global-scope-model and
Laravel Eloquent Query Builder Default Where Condition
<?php
class Product extends Eloquent {
public function newQuery()
{
return parent::newQuery()->where('permission', '=', 1);
}
}
You can use a Query Scope for this. Scopes allow you to easily re-use query logic in your models. To define a scope, simply prefix a model method with scope:
class User extends Eloquent {
public function scopePopular($query)
{
return $query->where('votes', '>', 100);
}
public function scopeWomen($query)
{
return $query->whereGender('W');
}
}
Utilizing A Query Scope
$users = User::popular()->women()->orderBy('created_at')->get();
I have News model, when i query news, i want it brings news where status = 1 as default.
News::all(); // select * from news where status = 1
News::where('anotherColumn',2)->get(); // select * from news where status = 1 and where category = 2
Is this possible? What i want is so similar to soft delete feature (it gets where deleted_at is not null and if all data is wanted withTrashed function can be used).
I looked docs but i couldn't find anything helpful. Also, i tried to handle it in construct at News model but it didn't worked either.
Thanks.
I normally override newQuery() for this. newQuery() is the method that Eloquent use to construct a new query.
class News extends Eloquent {
public function newQuery($excludeDeleted = true) {
return parent::newQuery($excludeDeleted)
->where(status, '=', 1);
}
}
Now your News::all() will only output your news with status = 1.
It's been already mentioned but here is a quick example using global scope which might be the best current solution since you wont have to override Eloquent methods and would result into the same behavior but with more control of your model.
Just add this to your model :
protected static function boot()
{
parent::boot();
static::addGlobalScope('exclude_deleted', function (Builder $builder) {
$builder->whereNull('deleted_at');
});
}
You can also create a child Scope class and reuse it for multiple Models.
For more information, Laravel doc explained pretty much everything about it:
https://laravel.com/docs/5.6/eloquent#global-scopes
I think the closes you'll get, without actually going in to change some core files...
is Query Scope...
Scopes allow you to easily re-use query logic in your models. To define a scope, simply prefix a model method with scope:
class News extends Eloquent {
public function scopeStatus($query)
{
return $query->where('status', '=', 1);
}
}
Utilizing that scope
$news = News::status()->get();
$news2 = News::status()->where('anotherColumn',2)->get();
Its not quite what you wanted...but its definitely a little shorter than typing
News::where('status','=',1)->get();
over and over