Laravel / Eloquent hasMany relationship with no foreign key - laravel

I have a model (Client) with a hasMany relationship to another (Client_option).
The two tables are in different databases (so there is a list of clients, and then each client has their own database with an options table within).
In my Client class I want my options() method to return the entire contents of the options table (it knows which client db to look for). As it is I get an error because the column client_id does not exist in the options table. I can of course create that column and populate every row with the client's id, but I'd only be doing it to keep Eloquent happy so would rather avoid that little messiness.
Thanks in advance for any input!
Geoff

This will allow you to work with it as a relation, call it as dynamic property $user->options, bulk save with push method and so on:
public function options()
{
// it will use the same connection as user model
$options = ClientOption::on($this->getConnectionName())->get();
// if options model has its own, then simply
// $options = ClientOption::get();
$this->setRelation('options', $options);
return $options;
}
public function getOptionsAttribute()
{
return (array_key_exists('options', $this->relations))
// get options from the relation, if already loaded
? $this->getRelation('options')
// otherwise call the method and load the options
: $this->options();
}

Related

Eager Loading with filters (where clauses)?

Is it possible to add where clause to an eager loading method on a model? For example:
class User extends Model {
public function sources()
{
return $this->hasManyThrough(
Source::class, // The related model
UserNetwork::class, // The intermediate table that connects this model with the related model.
'user_id', // The intermediate table's column that connects to this model.
'network_id', // The related table's column that connects to the secondLocalKey.
'id', // This model's column that connects to the firstKey.
'network_id' // The intermediate table's column that connects the related model.
)
->where('xyz_id', $this->xyz_id);
}
}
Doing this User::with('sources')->get() returns a collection of users with the sources relation but the sources collection is empty. When I run User::first()->sources()->get() it returns just the collection of sources with the actual data.
Isn't eager loading supposed to 'eager load' all the records instead of having to specify the the first() record?

Laravel Create multiple records in Pivot table

I'm trying to create a function in our Laravel 5.8 app that would add multiple records to a pivot table. At present we have the following setup;
Users
Training Courses
Users Training Courses (pivot table for the above relationships, with a few extra fields)
I want to be able to show all users in the database, then check their name, pick a training course and hit "Add" and it'll create a record in the pivot table for each user that was selected.
I can't figure out where to start with this - it seems like I need to have a "for each user selected, run the store function" loop in the controller, but I have no idea where to start.
I wasn't sure if there was an easy way to do this in eloquent or not. Is there a simple way to do this?
Eloquent does this automatically if you set up the relationships correctly and you don't have to worry about pivot tables.
class Users
{
public function trainingCourses()
{
return $this->hasMany(TrainingCourses::class);
}
}
class TrainingCourses
{
public function user()
{
return $this->belongsTo(User::class);
}
}
Then you can use the save() method to create the relationship. But I find it better to wrap this function inside a helper method that you can use throughout your code:
class Users
{
...
public function assignTrainingCourse(TrainingCourse $trainingCourse)
{
return $this->trainingCourses()->save($trainingCourse);
}
}
In your code, you could then do something as simple as this:
$user = User::find(1);
$trainingCourse = TrainingCourse::find(1);
$user->assignTrainingCourse($trainingCourse);
Building on this, suppose you have the following route to assign a training course, where it expects a trainingcourse_id in the request:
Route::post('/users/{user}/trainingcourses', 'UserTrainingCoursesController#store');
Thanks to route model binding, Laravel can inference the parent model (user) from the URL, and your controller might look like this:
// UserTrainingCoursesController.php
public function store(User $user)
{
$trainingCourse = TrainingCourse::find(request()->input('trainingcourse_id'));
$user->assignTrainingCourse($trainingCourse);
return back();
}
Of course, you'll want to put some validation in here, but this should get you started.

Does Eloquent handle caching of related entities?

I'm exploring Laravel's Eloquent as a drop-in replacement for my project's current, home-grown active record data layer. Currently, I have a class User that supports a many-to-many relationship with another class, Group. My current implementation looks something like:
class User {
protected $_groups; // An array of Group objects to which this User belongs
public function __construct($properties = []){
...
}
public function groups() {
if (isset($_groups))
return $_groups;
else
return $_groups = fetchGroups();
}
private function fetchGroups() {
// Lazily load the associated groups based on the `group_user` table
...
}
public function addGroup($group_id) {
// Check that the group exists and that this User isn't already a member of the group. If so, insert $group_id to $_groups.
...
}
public function removeGroup($group_id) {
// Check that the User is already a member of the group. If so, remove $group_id from $_groups.
...
}
public function fresh() {
// Reload user and group membership from the database into this object.
...
}
public function store() {
// Insert/update the user record in the `user` table, and insert/update/delete records in `group_user` based on the contents of `$_group_user`.
...
}
public function delete() {
// If it exists, delete the user record from the `user` table, and delete all associated records in `group_user`.
...
}
}
As you can see, my class:
Performs lazy loading of related groups, caching after the first time they're queried;
Maintains an internal representation of the User's relationship with their Groups, updating in the database only when store is called;
Performs sanity checks when establishing relationships, making sure that a Group exists and is not already related to the User before creating a new association.
Which, if any of these things, will Eloquent automatically take care of for me? Or, is my design flawed in some way that Eloquent can solve?
You can assume that I will re-implement User as User extends Illuminate\Database\Eloquent\Model and use Eloquent's belongsToMany as a replacement for my current fetchGroups method.
Eloquent caches the results of relationships internally, yes. You can see that in action in the Model::getRelationValue() method.
Eloquent also provides you with methods to help you manage the many-to-many relationship. You could implement this functionality inside your existing API. However, here are some things to look out for:
When using attach(), detach(), etc., the queries are performed immediately. Calling the parent User::save() method would only save the User's details, not the many-to-many relationship information. You could work around this by storing the IDs passed to your API temporarily, and then act upon them when you call User::store().
No sanity checks are performed when using attach/detach/etc. It would be good to apply these in your API if they're needed.
Adding or removing an ID to/from a many-to-many relationship will not affect the cached results of the initial relationship query. You would have to add in logic to insert or remove the related models into the collection.
For example, let's say a User has two Groups. When I load the user, I can access those groups using $user->groups. I now have a Collection of Groups cached inside the User model. If I call for $user->groups again, it will returned this cached Collection.
If I remove one Group using $user->detach($groupId), a query will be performed to update the join table, but the cached Collection will not change. Same goes for adding a group.

Laravel save / update many to many relationship

Can anyone help me on how to save many to many relationship? I have tasks, user can have many tasks and task can have many users (many to many), What I want to achieve is that in update form admin can assign multiple users to specific task. This is done through html multiple select input
name="taskParticipants[]"
The catch here is that through the same form (input) you can add/remove users, that's why I have to use sync().
Maybe I should start from the beginning but don't know where to start...
This is my User model:
public function tasks()
{
return $this->belongsToMany('Task','user_tasks');
}
Task model
public function taskParticipants()
{
return $this->belongsToMany('User','user_tasks');
}
TaskController
public function update($task_id)
{
if (Input::has('taskParticipants'))
{
foreach(Input::get('taskParticipants') as $worker)
{
$task2 = $task->taskParticipants->toArray();
$task2 = array_add($task2,$task_id,$worker);
$task->taskParticipants()->sync(array($task2));
}
}
}
This is structure of tables
tasks
id|title|deadline
user_tasks
id|task_id|user_id
tldr; Use sync with 2nd param false
Many-to-many relationship is belongsToMany on both models:
// Task model
public function users()
{
return $this->belongsToMany('User', 'user_tasks'); // assuming user_id and task_id as fk
}
// User model
public function tasks()
{
return $this->belongsToMany('Task', 'user_tasks');
}
In order to add new relation use attach or sync.
Difference between the two is:
1 attach will add new row on the pivot table without checking if it's already there. It's good when you have additional data linked to that relation, for example:
User and Exam linked with pivot table attempts: id, user_id, exam_id, score
I suppose this is not what you need in your situation:
$user->tasks()->getRelatedIds(); // [1,2,3,4,5,6]
$user->tasks()->attach([5,6,7]);
// then
$user->tasks()->getRelatedIds(); // [1,2,3,4,5,6,5,6,7]
2 sync on the other hand, will either remove all relations and set them up anew:
$user->tasks()->getRelatedIds(); // [1,2,3,4,5,6]
$user->tasks()->sync([1,2,3]);
// then
$user->tasks()->getRelatedIds(); // [1,2,3]
or it will setup new relations without detaching previous AND without adding duplicates:
$user->tasks()->sync([5,6,7,8], false); // 2nd param = detach
// then
$user->tasks()->getRelatedIds(); // [1,2,3,4,5,6,7,8]
Here's my notes on how to save and update on all the Eloquent relationships.
in One to One:
You have to use HasOne on the first model and BelongsTo on the second model
to add record on the first model (HasOne) use the save function
example:    $post->comments()->save($comment);
to add record on the second model (BelongsTo) use the associate function
example:    $user->account()->associate($account);    $user->save();
in One to Many:
You have to use HasMany on the first model and BelongsTo on the second model
to add record on the first table (HasMany) use the save or saveMany functions
example:    $post->comments()->saveMany($comments);
to add record on the second model (BelongsTo) use the associate function
example:    $user->account()->associate($account);    $user->save();
in Many to Many:
You have to use BelongsToMany on the first model and BelongsToMany on the second model
to add records on the pivot table use attach or sync functions
both functions accepts single ID or array of ID’s 
the difference is attach checks if the record already exist on the pivot table while sync don’t
example: $user->roles()->attach($roleId);
in Polymorphic One to Many:
You have to use MorphMany on the main model and MorphTo on all the (***able) models
to add records on all the other models use the save
example:    $course->tags()->save($tag);
the pivot table should have the following columns:
. main model ID
. (***able) ID
. (***able) Type
in Polymorphic Many to Many:
You have to use MorphByMany on the main model and MorphToMany on all the (***able) models
to add records on all the other models use the save or saveMany
example:    $course->tags()->save($tag);
example:    $course->tags()->saveMany([$tag_1, $tag_2, $tag_3]);
the pivot table should have the following columns:
. main model ID
. (***able) ID
. (***able) Type
in Has Many Through (shortcut):
You have to use HasManyThrough on the first table and have the normal relations on the other 2 tables
this doesn’t work for ManyToMany relationships (where there’s a pivot table)
however there’s a nice and easy solution just for that.
Here's an article I wrote, inspired by this answer. Important to check it: https://hackernoon.com/eloquent-relationships-cheat-sheet-5155498c209
syncWithoutDetaching([$id_one, $id_two, $id_three]); is what you are looking for. Actually it does the exact thing [sync with 2nd param false] does!
Solved: Use the updateOrInsert(array $attributes, array $values = [])
DB::table('your_pivot_table')->updateOrInsert([
'col' => $someValue
],[
'otherColumn' => $otherVlaue,
]);
}
The sync function obliterates the exiting relationships and makes your array the entire list of relations. You want attach instead to add relations without removing others.
for those who are searching for adding pivot attributes (the middle table attributes), you can use syncWithPivotValues and it also has the second parameter like this
$user->tasks()->syncWithPivotValues($tasksIDs,['day_number' => $day],false);

Laravel 4: How to add more data to Auth::user() without extra queries?

I'm rather new to Laravel 4 and can't seem to find the right answer, maybe you can help:
A User in our application can have many Accounts and all data is related to an Account, not a User. The account the User is currently logged into is defined by a subdomain, i.e. accountname.mydomain.com.
We added a method account() to our User model:
/**
* Get the account the user is currently logged in to
*/
public function account()
{
$server = explode('.', Request::server('HTTP_HOST'));
$subdomain = $server[0];
return Account::where('subdomain', $subdomain)->first();
}
The problem is that there is always an extra query when we now use something like this in our view or controller:
Auth::user()->account()->accountname
When we want to get "Products" related to the account, we could use:
$products = Product::where('account_id', Auth::user()->account()->id)->get();
And yet again an extra query...
Somehow we need to extend the Auth::user() object, so that the account data is always in there... or perhaps we could create a new Auth::account() object, and get the data there..
What's the best solution for this?
Thanks in advance
Just set it to a session variable. This way, you can check that session variable before you make the database call to see if you already have it available.
Or instead of using ->get(), you can use ->remember($minutes) where $minutes is the amount of time you wish to keep the results of the query cached.
You should take a look at Eloquent relationships : http://laravel.com/docs/eloquent#relationships
It provides simple ways to get the account of a user and his products. You said that a user can have many accounts but you used a first() in your function I used a hasOne here.
Using Eloquent relationships you can write in your User model:
<?php
public function account()
{
// I assume here 'username' is the local key for your User model
return $this->hasOne('Account', 'subdomain', 'username');
}
public function products()
{
// You should really have a user_id in your User Model
// so that you will not have to use information from the
// user's account
return $this->hasMany('Product', 'account_id', 'user_id');
}
You should define the belongsTo in your Account model and Product model.
With Eager Loading you will not run a lot of SQL queries : http://laravel.com/docs/eloquent#eager-loading
You will be able to use something like
$users = User::with('account', 'products')->get();
To get all users with their account and products.
I think this is a good example for the purpose of Repositories.
You shouldn't query the (involved) models directly but wrap them up into a ProductRepository (or Repositories in general) that handles all the queries.
For instance:
<?php
class ProductRepository
{
protected $accountId;
public function __construct($accountId)
{
$this->accountId = $accountId;
}
public function all()
{
return Product::where('account_id', $this->accountId)->get();
}
}
//now bind it to the app container to make it globaly available
App::bind('ProductRepository', function() {
return new ProductRepository(Auth::user()->account()->id);
});
// and whenever you need it:
$productRepository = App::make('ProductRepository');
$userProducts = $productRepository->all();
You could group the relevant routes and apply a filter on them in order to bind it on each request so the account-id would be queried only once per repository instance and not on every single query.
Scopes could also be interesting in this scenario:
// app/models/Product.php
public function scopeCurrentAccount($query)
{
return $query->where('account_id', Auth::user()->account()->id);
}
Now you could simply call
$products = Product::currentAccount()->get();

Resources