I've inherited a Laravel 5 project at work and would like to know how I should check for the existence of a related model to avoid null exceptions.
BlockDate model
class BlockDate extends Model {
public function claims()
{
return $this->belongsToMany(User::class);
}
}
User model
class User extends Model {
public function blocks()
{
return $this->belongsToMany(BlockDate::class);
}
}
Pivot table
$table->unsignedInteger('user_id');
$table->foreign('user_id')->references('id')->on('users');
$table->unsignedInteger(block_date_id');
$table->foreign('block_date_id')->references('id')->on(block_dates);
Users can claim a range of dates for vacation requests. However, users may have no claims or dates may not have been claimed. I am currently using
if ($user->blocks->count() > 0) {
$dates = $user->blocks->sortByDesc('created_at');
// more logic here....
}
I do not like using count everywhere, is there a way to incorporate the check like:
// I don't know what hasClaimedDates might be
$dates = $user->hasClaimedDates()->blocks->sortByDesc('created_at');
You can use the actual relationship method instead of the magic accessor:
$sortedDates = $user->blocks()->latest()->get();
This will give you an empty collection if no relations are established, but it will not fail on the sorting.
Note: latest() is an equivalent for orderBy('created_at', 'desc') in this case.
By the way, if you use $user->blocks->count(), it will first load all related models into memory and then count on the relation. If you are going to use the related models afterwards, that is fine. But if you don't and you only count them, this is a waste of resources. In this case $user->blocks()->count() is way more performant as it executes a database query that only returns a single number. Take this into consideration especially where you have a lot of related models.
Laravel offers an optional helper method to guard against nulls:
// will return either a collection or null
$dates = optional($user->blocks)->sortByDesc('created_at');
Related
How do I alter the query builder everytime I'm getting a new model?
I've found that overriding the method below works, but I'm not feeling good doing this. Is there a better way to do this?
So I want ->join() to be executed everytime for a specific model.
!! I don't want to use the protected $with = []; property, cause I don't want extra queries to be executed when not necessary.
public function newQueryWithoutScopes()
{
$builder = $this->newEloquentBuilder(
$this->newBaseQueryBuilder()
);
// Once we have the query builders, we will set the model instances so the
// builder can easily access any information it may need from the model
// while it is constructing and executing various queries against it.
return $builder->setModel($this)->join('join statement comes here')->with($this->with);
}
To affect the query every time a model is used, you can use the newQuery method.
public function newQuery($excludeDeleted = true){
return parent::newQuery()->join('orders', 'users.id', '=', 'orders.user_id');
}
But this will probably break Eloquent since your Model isn't representative of your database model anymore; it is now a Frankenmodel of two separate models.
If I have a model that needs to have a property that is an array of different models. Is there an eloquent method or way to handle this kind of problem?
eg.
I have a Feature model that needs a method that gets an array of objects that are from different models.
class Feature extends Model
{
public function getArrayOfDifferentObjects()
{
$array_of_objects=array();
???? ELOQUENT to get objects from different models ????
return $array_of_objects;
}
}
I have a feature_model_connections table with the following:
feature_id
featured_model_id
featured_model_type
The featured_model_type value would be a string denoting the model type.
The model_id would be a foreign key of the relevant model's table.
However I can't see how you would be able to use eloquent to return data for the getArrayOfDifferentObjects method in features model.
Any pointers would be much appreciated. Many thanks, J
What you are describing there, is basicly a Polymorphic Relations, which can handle these cases, and making fetching them easy, instead of i'm making a made up case, read the documentation, it is well written, under the section Polymorphic Relations. https://laravel.com/docs/5.1/eloquent-relationships#polymorphic-relations
Within your scope right now, you can do something like this.
public function getArrayOfDifferentObjects()
{
$objects = [];
$features = DB::table('feature_model_connections')
->select('feature_id', 'featured_model_id', 'featured_model_type')->get();
foreach($features as $feature)
{
$type = '\\App\\' . $feature->featured_model_type; //App is you app namespace
$model = $type::find($feature->featured_model_id);
if($model)
$objects[] = $model;
}
return $objects;
}
The basics of this, is you can define different types, with the app namespace seed, from there staticly call them, which will access the predefined type in your database table, then find the element and add it to the array. With that said, this is done as of the top of my head, no compile check, not ranned in Laravel, but it should pretty much get you the idea of what to do, with that said, if you can change your structure, go with the Polymorphic Relations, it is really awesome.
My Association model looks like this (irrelevant code redacted):
class Association extends Model
{
public function members() {
return $this->hasMany('App\Member');
}
}
My Member model looks like this:
class Member extends Model
{
public function scopeActive($query) {
return $query->where('membership_ended_at', Null);
}
public function scopeInactive($query) {
return $query->whereNotNull('membership_ended_at');
}
}
This is what I want to be able to do:
$association = Association::find(49);
$association->members->active()->count();
Now, I'm aware there's a difference between a Query and a Collection. But what I'm basically asking is if there's some kind of similar scope for collections. Of course, the optimal solution would be to not have to write TWO active methods, but use one for both purposes.
(question already answered in the comments, but might as well write a proper answer)
It is not possible to use a query scope in a Colletion, since query scope is a concept used in Eloquent to add constraints to a database query while Collections are just a collection of things (data, objects, etc).
In your case, what you need to do is to change this line:
$association->members->active()->count();
to:
$association->members()->active()->count();
This works because when we call members as a method, we are getting a QueryBuilder instance, and with that we can start chaining scopes to the query before calling the count method.
I have 3 models in my Laravel 4 project: Employee, EmployeeClass, Employer:
class Employee extends Eloquent {
protected $table = 'users';
public function employee_class () {
return $this->belongsTo('EmployeeClass', 'employee_class_id');
}
}
class EmployeeClass extends Eloquent {
protected $table = 'employee_classes';
public function employees () {
return $this->hasMany('Employee');
}
public function employer () {
return $this->belongsTo('Employer');
}
}
class Employer extends Eloquent {
protected $table = 'employers';
public function employee_classes () {
return $this->hasMany('EmployeeClass');
}
}
The EmployeeClass relationships work as expected. I can execute EmployeeClass::find(1)->employees; or EmployeeClass::find(1)->employer; and it returns the object.
Trying to make the same calls on the other two (to retrieve the relationship to EmployeeClass) doesn't work. Both of these lines return empty sets:
Employee::find(1)->employee_class;
Employer::find(1)->employee_classes;
However, the weird thing is, both of these lines work correctly:
Employee::find(1)->employee_class()->first();
Employer::find(1)->employee_classes()->first();
The first example returns NULL (I believe it should be returning a Collection). The second example returns an EmployeeClass object (the expected instance).
I want to point out that there is one entry in each table with an id of 1, and each one is set up with the FK = 1 as well, so they should join properly. In fact, I think the fact that EmployeeClass works correctly, and the fact that getting the query and executing it (in the second, successful, set of code) does as well, sort of proves that.
I'm sure I'm just doing something stupid; maybe another set of eyes will help!
I can use the workaround (the second set of code) since it seems to be working but I'd like to get it clean and correct if at all possible...
For multi-word relationships, the function should be in camelCase (in fact, all class methods should). When accessing a model's attributes, it is still allowed to access the relationship name in snake case (in your example, 'employee_class', but note that this bypasses all eager loading and you should access the relationship in exactly the same case as the relationship method's name.
In your example, if you rename the employee_class(es) functions to employeeClass(es), everything should work.
// gets all employees and their class(es). the argument(s) for with()
// MUST match the names of the methods exactly.
Employee:with('employeeClass')->get();
// you MUST access eager loaded data in the same case as in with().
// if you access via snake case, eager loading is bypassed.
$employee->employeeClass;
// this also works but should generally be avoided.
Employee::find(1)->employeeClass()->first();
I have a pair of objects in laravel, pages and contents.
I have setup a relationship function, and include, which includes contents based on page_id.
I want to apply other conditions such as where deleted - 0, and where dates are within certain bounds, I know how to apply where conditions and set these field up.
What I can't fathom is how extra conditions can be applied as well as matching relationship fields.
Could anybody help me with this?
Defining the relationship in the model is all you need to do to the model itself. However you can add a static function to the model to get the data with the information you need.
Page model methods examples:
class Page extends Eloquent {
public function contents()
{
return $this->has_many('Content');
}
// Return all content that has not been marked as
// deleted in the database
public static function with_contents($id)
{
return Page::find($id)->contents()
->where_null('deleted')
->get();
}
}
This example assumes that the deleted field in the contents table is null unless it is deleted, in which case it will have any value.
To use you would simply use
$page = Page::with_contents('1');
You can create as many of these static helper methods as you like or add as many conditions to the one method as you like, then use your new static methods to retrieve your data.
I think this might be what you're looking for
http://doginthehat.com.au/2012/06/adding-conditions-to-relationships-in-laravel/
class User extends Eloquent {
public function meta()
{
return $this->has_many('Meta','target_id')
->where('target_type','=',$this->table());
}
}