Laravel dynamic pivot table - php

In my app I use custom class for replicate tables during the tests. This class create new tables with _test postfix and tell eloquent to work with them. But when I work with "many to many" relations I need to specify pivot table name too. Is it possible to change pivot table during runtime application?

If I have understood your question correctly, then you want to be able to change your tables in many-to-many relationship dynamically.
please pay attention to source code of belongsToMany relationship:
public function belongsToMany($related, $table = null, $foreignKey = null, $relatedKey = null, $relation = null)
{
// If no relationship name was passed, we will pull backtraces to get the
// name of the calling function. We will use that function name as the
// title of this relation since that is a great convention to apply.
if (is_null($relation)) {
$relation = $this->guessBelongsToManyRelation();
}
// First, we'll need to determine the foreign key and "other key" for the
// relationship. Once we have determined the keys we'll make the query
// instances as well as the relationship instances we need for this.
$instance = $this->newRelatedInstance($related);
$foreignKey = $foreignKey ?: $this->getForeignKey();
$relatedKey = $relatedKey ?: $instance->getForeignKey();
// If no table name was provided, we can guess it by concatenating the two
// models using underscores in alphabetical order. The two model names
// are transformed to snake case from their default CamelCase also.
if (is_null($table)) {
$table = $this->joiningTable($related);
}
return new BelongsToMany(
$instance->newQuery(), $this, $table, $foreignKey, $relatedKey, $relation
);
}
you can define the table there. So I suggest you to do as following:(consider a Many-to-many relationship between User and Company model)
public function users(){
$table = env('APP_ENV') == 'production' ? 'table_name' : 'test_table_name';
$this->belongsToMany(User::class, $table);
}
and do the same for Company model
I never tested this but basically it should work.

Related

Laravel Eloquent one-to-one relationship returning empty collection

I am on a project where I am using custom PKs and FKs, and I'm trying to set up a one to one relationship.
For example, in Employee.php:
public function title()
{
return $this->hasOne('App\Title', 'TitleID');
}
On Tinker, I can retrieve an employee TitleID like so:
$employee = Employee::first();
$employee->TitleID;
Which returns:
"6"
I have now made a model: Title.php:
class Title extends Model
{
protected $table = "dbo.title";
protected $primaryKey = 'TitleID';
}
I can retrieve the contents of this model correctly when running $title = Title::all(); in Tinker.
I have set up a new relationship in Employee.php:
public function title()
{
return $this->hasOne('App\Title', 'TitleID');
}
However, in Tinker (which I have restarted) when I run:
$employee = Employee::first();
$employee->title()->get();
It returns:
Illuminate\Database\Eloquent\Collection {#3027
all: [],
}
What have I done to set up this relationship incorrectly?
The issue was that because the primary key of the other table isn't id, it wasn't able to find the collection.
However, according to the documentation, it reads:
Additionally, Eloquent assumes that the foreign key should have a value matching the id (or the custom $primaryKey) column of the parent.
So I assumed that because I set a custom $primaryKey value, it would intuitively be able to find it - however it seems the issue was to do with the local key's name breaking Eloquent's convention.
I solved the issue by declaring both the foreign and local key respectively:
public function title()
{
return $this->hasOne('App\Title', 'TitleID', 'TitleID');
}
You just need to access the property title instead of calling title():
$employee = Employee::first();
$employee->title;

Laravel Eloquent. Same relation, different type of return

I think there is basically no difference between both eloquent relations but work differently.. I can't find what I missed..
There are three model Concept, Attribute, Status. Those models have its own MySQL table concepts, attributes, statuses.
Concepts Table
id : integer(10) unsigned primary
name, description, .. : string(various) nullable
Attributes Table
id : integer(10) unsigned primary
concept_id : integer(10) unsigned foreign on concepts.id
Statuses Table
id : integer(10) unsigned primary
parent_id : integer(10) unsigned foreign on concepts.id
To define eloquent relation, I sorted things like below.
Concept has many Attribute
Concept has many Status
So naturally, I write each relation bidirectionally on each model.
Inside model Concept.php file..
public function attributes()
{
return $this->hasMany('App\Attribute');
}
public function statuses()
{
return $this->hasMany('App\Status');
}
Inside Attribute.php file..
public function concept()
{
return $this->belongsTo('App\Concept');
}
Inside Status.php file..
public function parent()
{
return $this->belongsTo('App\Concept');
}
Problem happens when I use method $concept->attributes.
On Concept.php file..
public function inherit()
{
// Copy basic concept data. (name, description, etc.)
$children = $this->replicate();
$children->name .= ' (Inherited)';
$children->inherit_parent_id = $this->id;
$children->inherit_origin_id = $this->inherit_origin_id ?? $this->id;
$children->save();
// Inherit Statuses
foreach( $this->statuses as $status ) {
$status->inheritTo( $children );
};
// Inherit Attributes
foreach( $this->attributes as $attribute ) {
$attribute->inheritTo( $children );
};
return $children;
}
As far as I know, when the first foreach was executes, $this->statuses returns Collection object. However, on the second foreach block, $this->attributes returns Concept object. So I try $this->attributes()->get() then it works fine.
Why those two foreach block works differently?
$this->attributes is a inherited property of Model class. So it shows these instead the relation.
If you rename your function to refer to the relation, it should work out for you.
Inside model Concept.php file (just an example name)
public function rel_attributes()
{
return $this->hasMany('App\Attribute');
}
And you refer to the relation like that:
// Inherit Attributes
foreach( $this->rel_attributes as $attribute ) {
$attribute->inheritTo( $children );
};
I agree with Philip's answer. You need to always define relations in eloquent that don't override the default variables.

Eloquent relationship where values are formatted differently

We've recently switched to a new permissions system on a project I am working on.
I have completed integration for this, eloquent relationships and all, when the requirements of the integration changed a little.
The permission system integrates with all of our systems across our infrastructure, and when it comes to referencing users from the Laravel project, the value in the permissions system is slightly different; in that it is prefixed with user-.
For example, a user with the username james in my users table is referenced as user-james in the permissions system table.
Is there any way to specify the value the eloquent relationship should look at?
I could just add a column to the users table to store the primary key of this user as it exists in the permissions table, but I wanted to see if there was a way to do this with eloquent.
If we consider relation is one - one we can do something like below:
First extend BelongsTo relation and change condition on where clause:
class CustomBelongsTo extends BelongsTo
{
/**
* #inheritDoc
*/
public function addConstraints()
{
if (static::$constraints) {
// For belongs to relationships, which are essentially the inverse of has one
// or has many relationships, we need to actually query on the primary key
// of the related models matching on the foreign key that's on a parent.
$table = $this->related->getTable();
$this->query->where($table.'.'.$this->otherKey, '=', 'user-'.$this->parent->{$this->foreignKey});
}
}
}
Then override belongsTo method on your model to use this custom relation.
class User extends Model {
protected $table = 'users';
public function permissions(){
return $this->belongsTo(Permission:class, 'username');
}
public function belongsTo($related, $foreignKey = null, $otherKey = null, $relation = null)
{
// If no relation name was given, we will use this debug backtrace to extract
// the calling method's name and use that as the relationship name as most
// of the time this will be what we desire to use for the relationships.
if (is_null($relation)) {
list($current, $caller) = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2);
$relation = $caller['function'];
}
// If no foreign key was supplied, we can use a backtrace to guess the proper
// foreign key name by using the name of the relationship function, which
// when combined with an "_id" should conventionally match the columns.
if (is_null($foreignKey)) {
$foreignKey = Str::snake($relation).'_id';
}
$instance = new $related;
// Once we have the foreign key names, we'll just create a new Eloquent query
// for the related models and returns the relationship instance which will
// actually be responsible for retrieving and hydrating every relations.
$query = $instance->newQuery();
$otherKey = $otherKey ?: $instance->getKeyName();
return new CustomBelongsTo($query, $this, $foreignKey, $otherKey, $relation);
}
}
I hope this help.

Laravel Models Pivot Table 1-1 Relationship should be 1-M

I have a problem here with my Laravel model, i'm trying to use a Pivot Table that I already have in my database, here's the layout of the tables I am using
clients
jobs
clients-jobs
I believe the Error is in my model, I don't completely understand the eloquent syntax, I have been trying several other methods though. I would like to keep the primary on the clients-jobs table in order to index it easier.
Here's my models:
Client
protected $table = 'clients';
public $timestamps = true;
protected $primaryKey = "id";
public function clientsjobs() {
return $this->belongsTo('ClientsJobs');
}
Job
protected $table = 'jobs';
protected $fillable = array('first_name', 'last_name', 'email');
protected $primaryKey = "id";
public function clientsjobs() {
return $this->belongsToMany('ClientsJobs');
}
ClientsJobs ( Maybe I should delete this model? Am I using it right? )
protected $table = 'clients-jobs';
protected $primaryKey = "id";
public function clients() {
return $this->hasOne('Client', 'id');
}
public function jobs() {
return $this->hasOne('Job', 'id');
}
The Code I am using to try and display all of the records for the clients-jobs table is this here in one of my controllers (thanks sebastien):
$masterArray = array();
ClientsJobs::with('clients', 'jobs')->chunk(200, function($records) use (&$masterArray) { //Chunk Retrieves 200 Records at a time
foreach ($records as $record) {
$masterArray[] = array(
'id' => $record->id, // id
'client_name' => !empty($record->clients) ? $record->clients->fname : "Unknown",
'job_name' => !empty($record->jobs) ? $record->jobs->name : "Unknown",
'wage' => $record->wage,
'productivity'=> $record->productivity,
);
}
});
return $masterArray;
this code works for the first two records, but "unknown" after that, I'm pretty sure that it's because the application thinks it's a 1:1 relationship ( I only have 2 Users and 2 Jobs as dummy data ).
Thanks in Advance for any suggestions that you can make, also if you see something nasty that I did please let me know
You should delete the ClientsJobs model, it's unnecessary. Laravel's belongsToMany relationship, when set up correctly will deal with the pivot table itself. Take a look at:
http://laravel.com/docs/4.2/eloquent
Many-to-many relations are a more complicated relationship type. An
example of such a relationship is a user with many roles, where the
roles are also shared by other users. For example, many users may have
the role of "Admin". Three database tables are needed for this
relationship: users, roles, and role_user. The role_user table is
derived from the alphabetical order of the related model names, and
should have user_id and role_id columns.
Your pivot table should either be named with Laravel's convention of taking the model name of each (Singular) and joining them together with an underscore character (client_job) or you can specify the name of the pivot table etc. on your relationship. Laravel's documentation gives the following example which allows you to override the default pivot table name and corresponding keys:
return $this->belongsToMany('Role', 'user_roles', 'user_id', 'foo_id');
In your case, if you're after a one to one relationship, you don't really need a pivot table. You can just implement hasMany and belongsTo relationships. That is, a client belongs to a job, but a job can have many clients.

Laravel Removing Pivot data in many to many relationship

Not sure if I set this up correctly. In Laravel I'm creating two models with a many-to-may relationship
The models are Item and Tags. Each one contains a belongsTo to the other.
When I run a query like so:
Item::with('tags')->get();
It returns the collection of items, with each item containing a tags collection. However the each tag in the collection also contains pivot data which I don't need. Here it is in json format:
[{
"id":"49",
"slug":"test",
"order":"0","tags":[
{"id":"3","name":"Blah","pivot":{"item_id":"49","tag_id":"3"}},
{"id":"13","name":"Moo","pivot":{"item_id":"49","tag_id":"13"}}
]
}]
Is there anyway to prevent this data from getting at
you can just add the name of the field in the hidden part in your model like this:
protected $hidden = ['pivot'];
that's it , it works fine with me.
You have asked and you shall receive your answer. But first a few words to sum up the comment section. I personally don't know why you would want / need to do this. I understand if you want to hide it from the output but not selecting it from the DB really has no real benefit. Sure, less data will be transferred and the DB server has a tiny tiny bit less work to do, but you won't notice that in any way.
However it is possible. It's not very pretty though, since you have to override the belongsToMany class.
First, the new relation class:
class BelongsToManyPivotless extends BelongsToMany {
/**
* Hydrate the pivot table relationship on the models.
*
* #param array $models
* #return void
*/
protected function hydratePivotRelation(array $models)
{
// do nothing
}
/**
* Get the pivot columns for the relation.
*
* #return array
*/
protected function getAliasedPivotColumns()
{
return array();
}
}
As you can see this class is overriding two methods. hydratePivotRelation would normally create the pivot model and fill it with data. getAliasedPivotColumns would return an array of all columns to select from the pivot table.
Now we need to get this integrated into our model. I suggest you use a BaseModel class for this but it also works in the model directly.
class BaseModel extends Eloquent {
public function belongsToManyPivotless($related, $table = null, $foreignKey = null, $otherKey = null, $relation = null){
if (is_null($relation))
{
$relation = $this->getBelongsToManyCaller();
}
$foreignKey = $foreignKey ?: $this->getForeignKey();
$instance = new $related;
$otherKey = $otherKey ?: $instance->getForeignKey();
if (is_null($table))
{
$table = $this->joiningTable($related);
}
$query = $instance->newQuery();
return new BelongsToManyPivotless($query, $this, $table, $foreignKey, $otherKey, $relation);
}
}
I edited the comments out for brevity but otherwise the method is just like belongsToMany from Illuminate\Database\Eloquent\Model. Of course except the relation class that gets created. Here we use our own BelongsToManyPivotless.
And finally, this is how you use it:
class Item extends BaseModel {
public function tags(){
return $this->belongsToManyPivotless('Tag');
}
}
If you want to remove pivot data then you can use as protected $hidden = ['pivot']; #Amine_Dev suggested, so i have used it but it was not working for me,
but the problem really was that i was using it in wrong model so i want to give more detail in it that where to use it, so you guys don't struggle with the problem which i have struggled.
So if you are fetching the data as :
Item::with('tags')->get();
then you have to assign pivot to hidden array like below
But keep in mind that you have to define it in Tag model not in Item model
class Tag extends Model {
protected $hidden = ['pivot'];
}
Two possible ways to do this
1. using makeHidden method on resulting model
$items = Item::with('tags')->get();
return $items->makeHidden(['pivot_col1', 'pivot_col2']...)
2. using array_column function of PHP
$items = Item::with('tags')->get()->toArray();
return array_column($items, 'tags');

Categories