I'm trying to create a model that has a relationship which is required for the object to be valid. Querying this model should not return any results that are missing this relationship. It seems like global scopes are the best option for this scenario, however I've been unable to make this work. Am I doing something wrong? Perhaps there's a better way?
Here is a simplified version of the model.
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Car extends Model
{
protected static function boot()
{
parent::boot();
static::addGlobalScope('has_details', function ($builder) {
$builder->has('details');
});
}
public function details()
{
return $this->hasOne(Details::class);
}
}
And here is a one-to-many relationship method on another model.
public function cars()
{
return $this->hasMany(Car::class);
}
Without the global scope, this code returns all related "cars", including ones without "details". With the global scope, no "cars" are returned. I want this code to only return "cars" with "details".
Thank you.
You have some mistakes at Anonymous Global Scopes declaration:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder;
class Car extends Model
{
protected static function boot()
{
parent::boot();
static::addGlobalScope('has_details', function (Builder $builder) {
$builder->has('details');
});
}
public function details()
{
return $this->hasOne(Details::class);
}
}
You might try eager-loading the relationship, so that the has() inspection will actually see something. (I suspect because the relationship is not loaded, the details relationship is never populated.)
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder;
class Car extends Model
{
protected $with = ['details'];
protected static function boot()
{
parent::boot();
static::addGlobalScope('has_details', function (Builder $builder) {
$builder->has('details');
});
}
public function details()
{
return $this->hasOne(Details::class);
}
}
Related
This is my Vote model:
namespace App;
use Illuminate\Database\Eloquent\Model;
class Vote extends Model
{
public function user(){
return $this->belongsTo('App\User');
}
public function options(){
return $this->hasMany('App\Option');
}
}
this is my Option model:
namespace App;
use Illuminate\Database\Eloquent\Model;
class Option extends Model
{
public function vote(){
return $this->belongsTo('App\Vote');
}
public function users(){
return $this->belongsToMany('App\User');
}
}
The case is i want to get the users data from many to many relationship in Option model, but started from Vote model. So i get the options data in the Vote model first, then i get the users data in Option model (many to many)
Laravel has no native support for a direct relationship.
I've created a package for cases like this: https://github.com/staudenmeir/eloquent-has-many-deep
class Vote extends Model
{
use \Staudenmeir\EloquentHasManyDeep\HasRelationships;
public function users()
{
return $this->hasManyDeep(User::class, [Option::class, 'option_user']);
}
}
I just create a Trait to generate slugs to my models:
<?php
namespace App\Traits;
trait SlugGenerator
{
public function slugGenerator($slug = null)
{
return $slug ?? 'UUIDGeneratorFunction';
}
public static function bootSlugGenerator()
{
static::creating(function ($model) {
// How to call slugGenerator() function here?
$model->slug = slugGenerator();
});
}
}
The question is: How to call the slugGenerator() function inside my trait boot?
And if I want to change the slug Generator from any model, the way I'm setting the $slug variable is right? Example:
<?php
namespace App\Models;
use App\Traits\SlugGenerator;
use Illuminate\Foundation\Auth\User as Authenticatable;
class User extends Authenticatable
{
use SlugGenerator;
public function slugGenerator($slug = null)
{
return 'customSlug';
}
}
The event receives the instance of the model, so you can call the method on that instance:
public static function bootSlugGenerator()
{
static::creating(function ($model) {
$model->slug = $model->slugGenerator();
});
}
Docs: https://laravel.com/docs/5.8/eloquent#events
I'm working with too many mysql large views. I don't want to use Eloquent Model for the views.
I created "ViewBalance extends Illuminate\Support\Facades\DB". Everything worked as I wanted.
But i need to set init() method for company scope.
How can I use the global scope without init() method?
ViewModel
<?php
namespace App\Models\Views;
use App\Facades\CoreService;
use Illuminate\Support\Facades\DB;
class ViewBalance extends DB
{
const COMPANY_COLUMN = 'company_id';
const TABLE = 'view_balances';
public static function init()
{
return parent::table(self::COMPANY_COLUMN)
->where(self::COMPANY_COLUMN, CoreService::companyId());
}
}
In Controller
<?php
$data = ViewBalance::init()->get(); // Worked!
I have answered my own question. Because, I don't want to edit my question for more complicate. I want to talk about a solution to this problem.
I added $table_view variable and getView() method in Laravel model. If you want, you can create trait for clean codes.
It can be accessed easily views. Also it is part of the main model.
For example;
Laravel Basic Account Model
class Account extends Model {
protected $table = 'accounts';
protected $table_view = 'view_accounts';
public function getView()
{
return \DB::table($this->table_view)->where('global_scope', 1);
}
}
Laravel Account Controller
class AccountController extends Controller {
public function index()
{
$items = (new Account)->getView()->paginate(20);
}
}
public function scopeActive($query)
{
return $query->where('active', true);
}
or
public function scopeInactive($query)
{
return $query->where('active', false);
}
How can i create a method in my model below that consist of first value of pages?
I want my cover method to get he first instance of pages.
Here's my model:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Book extends Model
{
protected $guarded = [];
public function pages()
{
return $this->hasMany('\App\Page');
}
public function cover()
{
//first page of pages here
}
}
Use the first() method on the relationship
public function cover()
{
return $this->pages()->first();
}
I'm retrieving Events from the database and every time I get events from the database I want to check where active_from <= today.
How am I able to define a global scope which will be used when I retrieve a model?
You need to create a trait like EventsTrait and inside that trait, add a bootEventTrait function that will add a global scope like EventsScope. Use SoftDeletingTrait as pattern.
EventTrait
trait EventsTrait {
public static function bootEventsTrait()
{
static::addGlobalScope(new EventsScope);
}
}
EventScope
use Illuminate\Database\Eloquent\ScopeInterface;
use Illuminate\Database\Eloquent\Builder;
class EventScope implements ScopeInterface {
public function apply(Builder $builder)
{
$builder->where("active_from","<=", "today");
}
public function remove(Builder $builder)
{
// remove scope
}
}
You can add a global scope (eg: is_active) to the Event model.
// App\Event.php
use Carbon\Carbon;
public static function boot(){
parent::boot();
static::addGlobalScope('is_active', function($builder){
$builder->where('active_from', '<=', Carbon::now());
})
}