I use models that extend a generic_model, which in turn extends Eloquent (so that I have several crud methods I use already set to be inherited).
A lot of the tables I use invoke soft delete, which needs a WHERE clause of...
WHERE deleted = 0
Is there a way to make Laravel behave in such a way that this WHERE clause is automatically included in all queries and all queries to objects that are related to one another?
e.g.
pages where id = 5 and deleted = 0
and then...
images where page_id = 5 and deleted = 0
if you're using laravel 3 this is what you needs
on your model
public function query()
{
$query = parent::query();
$query->where('deleted','=','0');
return $query;
}
if you're using laravel 4 just change the method query for newQuery
public function newQuery()
{
$query = parent::newQuery();
$query->where('deleted','=','0');
return $query;
}
reference
In relationships you can add the where_clause in your return:
public function pages()
{
return $this->has_many('Page')->where_deleted(0);
}
In your Model, you could add something like:
public static function active()
{
return self::where_delete(0)->get();
}
to use Page::active() instead of Page::all()
(Or you can remove the ->get() from the function in the model, so you can still further modify your query (Page::active()->order_by('name'))
Related
I have a model - let's call it Parent.
Each Parent can have many children.
public function children(): HasMany
{
return $this->hasMany(Child::class, 'parent_id', 'id');
}
I want to make a scope for Parent called "active".
My requirement goes as follows:
Parent is active when it has at least 1 child.
I've done local scopes multiple times and I know that within Parent.php I can do something like:
public function scopeActive($query)
{
return $query->where(…);
}
But as you see, $query is a \Illuminate\Database\Eloquent\Builder which is not my case. I want to operate on related model somehow, not the DB query.
Can I do this in a clean way without fetching all parents with('child') and within ->filter() forgetting those that have no children? When there will be 1000 parents but only one will have children, it will simply waste DB resources (by fetching 999 redundant parents).
You can use the following query
public function scopeActive($query)
{
return $query->has('children');
}
One option is to use whereHas in scope:
public function scopeActive($query){
return $query->whereHas('children');
}
OR
public function scopeActive(){
return $this->whereHas('children');
}
And
Parent::active()->with('children')->get();
should give you all Parents who have children and along with respective children
Can you try something like:
public function getActiveAttribute() {
return $this->children()->count() > 1;
}
This can be used like,
$parent->active
And the query will only be made when you'll try to access the active property on the Parent model, not when you will be fetching all parents.
In Parent
public function getActiveAttribute() {
return $this-> children()->exists();
}
In code
$parent = new Parent;
if($parent->active){//true}
You can also use count
count($this->children); // 0 evaluates to false
a related thread here if you need:
Laravel Check If Related Model Exists
I have a scope on one of my laravel models to return data related to a model contained in another table:
public function scopeWithShortlistedApplications($query)
{
$query->with('shortlisted_applications:shift_id,user_id,shortlisted');
}
Here, shortlisted_applications is the relationship and refers to a table called shift_user which has the Application model attached to it, defined as follows.
public function shortlisted_applications()
{
return $this->hasMany(Application::class, 'shift_id');
}
What I want to do, is put a simple WHERE clause on the scope, so that it only returns instances where the "shortlisted" field is equal to 1 (this is a boolean value). I tried the following two techniques, but to not avail:
public function scopeWithShortlistedApplications($query)
{
$query->with('shortlisted_applications:shift_id,user_id,shortlisted')
->whereRaw('SELECT shortlisted from shift_user WHERE shift_id = "' . $this->id '"', 1);
}
and
public function scopeWithShortlistedApplications($query)
{
$query->with('shortlisted_applications:shift_id,user_id,shortlisted')
->where('shift_user.shortlisted', 1);
}
Does anyone know how to accomplish this in the scope function?
Eager loading constraints should be able to do it for you:
...->with(['shortlisted_applications' => function ($query) {
$query->where('shortlisted', 1);
}]);
This would still load the main result set but only eager load the relationship for those records if they have shortlisted == 1.
This is my Report Model
protected $fillable = [
'site_url',
'reciepients',
'monthly_email_date'
];
public function site()
{
return $this->belongsTo('App\Site');
}
This is my Site Model
public function report()
{
return $this->hasMany('App\Report');
}
This is my ReportController
public function showSpecificSite($site_name)
{
$records = DB::table('reports')
->select('email_date','url','recipient')
->whereHas('sites', function($query){
$query->where('site_name',$site_name);
})
->get();
return view('newsite')->with('records',$records)
->with('site_name',$site_name);
}
My Controller is not yet working as well.
The thing is I would like to copy all the three files from sites table to reports table.
Is it possible in insertInto ?
My code on ReportController shows you that I'm selecting data from reports table but I am the one who puts data to reports table to see the output but it is not yet working because of the it cant reach out the value of site_name even though I already put a relationship between the two tables.
You're not actually using Eloquent in your controller you're just using the Query Builder (DB). This will mean that you don't have access to anything from your Eloquent models.
Try:
$records = \App\Report::whereHas('site', function($query) use($site_name) {
$query->where('site_name', $site_name);
})->get(['id', 'email_date', 'url', 'recipient']);
I've added id to the list of columns as I'm pretty sure you'll need that to use whereHas.
NB to use a variable from the parent scope inside a closure you need to pass it in using use().
Is it possible to override the ->get() methods from Eloquent in order to always customize the output of the selected fields from the DB query?
For example, if you do a usual Eloquent query like:
User::where('email','like','%wherever.com')->get();
Instead of it making the query:
Select name, email, address, created_at from users where email like '%wherever.com'
Is it possible to override the method to always return something like:
Select CONCAT('CL::',name), lower(email), address, created_at from users where email like '%wherever.com'
I ask for ->get because I have seen that I can pass an array with the columns to be selected but I don't want to define them on all queries.
So, after digging around the functions regarding the query I found this solution:
I created a new class CustomQueryBuilder that extends the Illuminate\Database\Query\Builder
There you can override the get() / select() / where() methods from Eloquent.
Then, in the models that you want to change the way the query is made, define the fields to change like:
protected $encrypted = [
'name',
'email',
];
After this, I created a new class CustomModel that extends the Illuminate\Database\Eloquent\Model and there override the newBaseQueryBuilder like this:
protected function newBaseQueryBuilder()
{
$connection = $this->getConnection();
return new CustomQueryBuilder(
$connection, $connection->getQueryGrammar(), $connection->getPostProcessor(), $this
);
}
Inside the CustomQueryBuilder you can customise all the methods from builder for your needs.
With this setup, you can in any Illuminate\Database\Eloquent\Model change it to extend your CustomModel and inherit this special behaviour for the designated columns.
Be aware that all queries made from Models extending your CustomModel will get this new methods, so do all the needed checks to don't mess up with Eloquent normal behaviour, something like this:
public function where($column, $operator = null, $value = null, $boolean = 'and')
{
if ($this->model !== null && isset($this->model::$encrypted))
{
if (in_array($column, $this->model::$encrypted))
{
$column = DB::raw("CONCAT('CL::',$column)");
}
}
parent::where($column, $operator, $value, $boolean);
}
PS: I know this sound silly with the CONCAT example but with the property $encrypted you can figure out that it's not for concatenating string.
You can use model to update the result for the specific field like below
Use this code in your model file to contact CL::' with thename` value
public function getNameAttribute($value) {
return 'CL::'.$value;
}
You can just call User::where('email','like','%wherever.com')->get(); like this
This getNameAttribute function will always return name value with "CL::"
So you don't need to add CONCAT('CL::'.name) with your query.
Same way you can add for other fields also
Updated
Solution when querying the result
Add this geofields in your model
protected $geofields = array("concat('CL::',name) as name");
Add this newQuery function to override the columns
public function newQuery($excludeDeleted = true)
{
$raw='';
foreach($this->geofields as $column){
$raw .= $column;
}
return parent::newQuery($excludeDeleted)->addSelect('*',\DB::raw($raw));
}
Hope this is what you expect.
Is it possible to have a hasMany relationship on two columns?
My table has two columns, user_id and related_user_id.
I want my relation to match either of the columns.
In my model I have
public function userRelations()
{
return $this->hasMany('App\UserRelation');
}
Which runs the query: select * from user_relations where user_relations.user_id in ('17', '18').
The query I need to run is:
select * from user_relations where user_relations.user_id = 17 OR user_relations.related_user_id = 17
EDIT:
I'm using eager loading and I think this will affect how it will have to work.
$cause = Cause::with('donations.user.userRelations')->where('active', '=', 1)->first();
I don't think it's possible to do exactly what you are asking.
I think you should treat them as separate relationships and then create a new method on the model to retrieve a collection of both.
public function userRelations() {
return $this->hasMany('App\UserRelation');
}
public function relatedUserRelations() {
return $this->hasMany('App\UserRelation', 'related_user_id');
}
public function allUserRelations() {
return $this->userRelations->merge($this->relatedUserRelations);
}
This way you still get the benefit of eager loading and relationship caching on the model.
$cause = Cause::with('donations.user.userRelations',
'donations.user.relatedUserRelations')
->where('active', 1)->first();
$userRelations = $cause->donations[0]->user->allUserRelations();
Compoships adds support for multi-columns relationships in Laravel 5's Eloquent.
It allows you to specify relationships using the following syntax:
public function b()
{
return $this->hasMany('B', ['key1', 'key2'], ['key1', 'key2']);
}
where both columns have to match.
I'd prefer doing it this way:
public function userRelations()
{
return UserRelation::where(function($q) {
/**
* #var Builder $q
*/
$q->where('user_id',$this->id)
->orWhere('related_user_id',$this->id);
});
}
public function getUserRelationsAttribute()
{
return $this->userRelations()->get();
}
If anyone landed here like me due to google:
As neither merge() (as suggested above) nor push() (as suggested here) allow eager loading (and other nice relation features), the discussion is still ongoing and was continued in a more recent thread, see here: Laravel Eloquent Inner Join on Self Referencing Table
I proposed a solution there, any further ideas and contributions welcome.
You can handle that things with this smart and easy way .
$cause = Cause::with(['userRelations' => function($q) use($related_user_id) {
$q->where('related_user_id', $related_user_id);
}])->where('active', '=', 1)->first();