Eloquent relationship where values are formatted differently - php

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.

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;

accessor considered as foreign key defined in OneToMany relations

My User model have these fields :
user_id
username
name
family
supervisor
And in that model I defined an accesssor that same name as supervisor attribute like this (because I want to return supervisor user as an User object and not a simple id):
public function getSupervisorAttribute($value)
{
return is_null($value) ? null : User::select('user_id', 'name', 'family')->find($value);
}
In the other hand there is a OneToMany relationship like this:
public function child()
{
return $this->hasMany(self::class, 'supervisor', 'user_id');
}
Now each time I call child() relation it return Illegal offset type error. seems that supervisor field does not recognized in second argument of hasMany method.
There is any way to solve this problem Without having to change accessor name.
I think the problem comes when you try to retrieve the relationship child, why? Because you have an accessor on your supervisor which is a foreign key inside of child relationship, so what happens is when you ask for that relationship, Laravel will try to use your supervisor property, since it has an accessor, it will trigger and instead of getting a desired property (which i guess is an integer), you will either get NULL or a User. I hope this clarifies it for you.
One workaround for this is to add appends attribute to your Model and then put mutators and accessors on that attribute.
If a User has children then it's one to many(he/she can have many children or none)
Anyway,
Lets assume you have a table named Children make sure you change the table name in the model(laravel assumes it's childrens in the DB).
If public function child() {} is in the User model then,
/*
* children since he/she can have many children
* hasMany means this model has many of the other model by self::class
* it's as if you're saying this model has many of this model so change it
*/
public function children()
{
/* you're building a relationship between User('user_id' as local primary key)
* and Children('parent_id' as foreign key)
* means children table has foreign key parent_id(unsignedInt)
* it returns an array of all the children objects of this User row
*/
return $this->hasMany('Children', 'parent_id', 'user_id');
}
On the other hand the Children Model:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Children extends Model
{
/**
* The table associated with the model.
*
* #var string
*/
protected $table = 'children';
public function parent()
{
// belongsTo means 'parent_id' in this model(Children) relates to 'user_id' on 'User' model
// it returns the User object which is the parent of this child row
return $this->belongsTo('User', 'user_id', 'parent_id');
}
}
This solution is for creating another table however it seems you want it with the same table(it's not very clear edit your post).
// this function makes no sense, it takes an integer and finds the parameter to was given
$userWithIdOne = $user->getSupervisorAttribute(1);
Give us the migrations of the table, show us the relationships.

Laravel dynamic pivot table

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.

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