My Document model has a custom primary key name, so I typed:
class Document extends Model {
protected $primaryKey = 'fluxo_mensagem_id';
After this, I suppose Laravel to know my primary key name and then I wouldn't expected to have to type it manually every time I need to set its value. Something like:
$document = new Document;
$document->setPrimary($pivot->id);
Instead of:
$document = new Document;
$document->fluxo_mensagem_id = $pivot->id;
For more details, my partial ER diagram:
Question 1
Laravel provides some method to set primary key values dynamically after custom names defined on model class?
Question 2
On document table, the column fluxo_mensagem_id is a foreign key that references id fluxo_mensagem pivot (intermediate) table. There's some way to "associate" (as we can do with Belongs To relationships) Pivot object to a Document model?
For example:
$pivot = $mensagem->fluxos()->first()->pivot;
$document = new Document;
$document->fluxoMensagem()->associate($pivot);
This would fit the problem because his intention is to set the foreign key on the child model.
Regarding Question 1: You can set the primary key value as any other, but you need to set the $incrementing = false, otherwise it'll be saved as 0.
/**
* Indicates if the IDs are auto-incrementing.
*
* #var bool
*/
public $incrementing = false;
Related
I have a BuildingImage model with a OneToOne relation to BuildingType:
BuildImage Model:
/**
* Get the source type of the Building Image.
*/
public function type()
{
return $this->hasOne('App\BuildingType');
}
BuildingType Model:
/**
* Get the Building Image that owns the building type.
*/
public function buildingImage()
{
return $this->belongsTo('App\BuildingImage');
}
My tables:
building_images table -> source is the building type id
building_types table
When I try to do this in my controller just to test:
(an ImageRequest has one or more Builings and a Building has one BuildingType)
$imageRequest = ImageRequest::findOrFail($id);
$buildings = $imageRequest->buildingImages;
foreach ($buildings as $building) {
dd($building->type);
}
I get this error:
SQLSTATE[42S22]: Column not found: 1054 Unknown column
'building_types.building_image_id' in 'where clause' (SQL: select *
from building_types where building_types.building_image_id = 45
and building_types.building_image_id is not null limit 1)
What am I doing wrong here?
That's because by default laravel will look for a primary key named {model}_id, and given that you are using a different column name (source), you need to specify when defining the relationship:
As the documentation states:
Eloquent determines the foreign key of the relationship based on the model name. In this case, the Phone model is automatically assumed to have a user_id foreign key. If you wish to override this convention, you may pass a second argument to the hasOne method:
return $this->hasOne('App\Phone', 'foreign_key');
Additionally, Eloquent assumes that the foreign key should have a value matching the id (or the custom $primaryKey) column of the parent. In other words, Eloquent will look for the value of the user's id column in the user_id column of the Phone record. If you would like the relationship to use a value other than id, you may pass a third argument to the hasOne method specifying your custom key:
return $this->hasOne('App\Phone', 'foreign_key', 'local_key');
Now that that is clear. Let's talk about the relationship itself.
You are defining that a BuildImage has one BuildingType. But with that logic, the foreign key should be stored in the building_types table, and not the other way around (source column appears in the building_images table). And -I'm just assuming that- many BuildImage can belongs to an specific BuildingType. So, if this assumption is correct:
a BuildImage belongs to a specific BuildingType.
a BuildinType can be specify in many BuildImages
So, you should define your relationship like this:
BuildImage.php
public function type()
{
return $this->belongsTo('App\BuildingType', 'source');
}
BuildingType.php
public function images()
{
return $this->hasMany(BuildingImage::class, 'source');
}
Your BuildImage model should be
/**
* Get the source type of the Building Image.
*/
public function type() {
return $this->hasOne('App\BuildingType',"id","source");
}
And BuildingType Model should be
/**
* Get the Building Image that owns the building type.
*/
public function buildingImage()
{
return $this->belongsTo('App\BuildingImage',"source","id");
}
This should work.
For more info have a look
https://laravel.com/docs/5.7/eloquent-relationships#one-to-one
Have you tried to indicate the index ID like this?
public function buildingImage()
{
return $this->belongsTo('App\BuildingImage', 'image_request_id');
}
Eloquent determines the foreign key of the relationship based on the
model name. In this case, the Phone model is automatically assumed to
have a user_id foreign key. If you wish to override this convention,
you may pass the second argument to the hasOne method:
return $this->hasOne('App\Phone', 'foreign_key');
https://laravel.com/docs/5.7/eloquent-relationships#one-to-one
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.
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.
Consider the following two relations:
User:
CREATE TABLE user (
id INT NOT NULL,
social_provider_id INT NOT NULL,
CONSTRAINT `user_social_provider_id_fk` FOREIGN KEY (`social_provider_id`)
REFERENCES `social_provider` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION,
)
Social provider:
CREATE TABLE social_provider (
id INT NOT NULL,
name VARCHAR NOT NULL )
Eloquent models:
class User extends Model{
protected $table = 'user';
public function socialProvider(){
return $this->hasOne('/SocialProvider');
}
}
Now I want to create a new User object and save it:
$user = new User();
$socialProvider = SocialProvider::find(1);
$user->socialProvider()->save($socialProvider);
$user->save();
But I get this exception on the line where I assign the SocialProvder object
Call to undefined method Illuminate\Database\Query\Builder::save()
I tried to save the User object first and then assign the relationship, but obviously this is not possible because of the NOT NULL constraint in the definition of the user table.
My workaround at the moment is to assign the relationship this way:
$user->social_provider_id = $socialProvider->id;
But I would like to use Eloquent's features.
How can I save a new model with a not nullable relationship, without having to assign IDs by myself?
Solution:
Like #Panagiotis Koursaris suggested, the solution is to use associate() instead of save()
Try this:
$user = new User();
$socialProvider = SocialProvider::find(1);
$user->socialProvider()->associate($socialProvider);
$user->save();
In User model, do this
public function socialProvider(){
return $this->hasOne('App/SocialProvider');
}
<?php
// Model
class ProfileDelivery extends \Eloquent {
protected $table = 'profile_delivery';
protected $guarded = array();
public $timestamps = FALSE;
}
// Somewhere
$deliveryGuy->id = 1;
print $deliveryGuy->id; // Prints 1
if (!$deliveryGuy->save()) {
throw new \Exception('Cant save .');
}
print $deliveryGuy->id; // Prints 0
Can anyone explain me why the ID value is lost?
Not sure if you solved this for your situation but in Laravel 5.1 this just happened to me - the primary key of one table is the same as the primary key to another table because there is a 0 or 1 - to - 1 relationship between them.
What is happening is that Eloquent is assigning the primary key to the last insert id of the insert, but because the primary key is not an auto increment value, it is assigning it to zero. It is stored in the database correctly, but the model after the save is not useful if you need to use that key. The solution is to override the insertAndSetId function for the model that has the foreign primary key to prevent its setting of the primary key attribute. Of course, you don't want to do this for any models that do have an auto-incrementing key, just models that you are assigning the primary key manually. It's also not necessary if you don't need to use the model immediately after creating it because as I said above the database has the correct info in it.
protected function insertAndSetId(Builder $query, $attributes)
{
$id = $query->insertGetId($attributes, $keyName = $this->getKeyName());
// $this->setAttribute($keyName, $id);
}
This is because your id column in the database probably does not have autoincrement set.
I tried this with a test model without autoincrement and it returns 0, but when I changed the id column to autoincrement it returned the id correctly.
Check this function in laravel/Framework/Src/Illuminate/Database/Eloquent/Model.php
It says it will insert and set id if it has autoincrement.
protected function performInsert($query)
{
if ($this->fireModelEvent('creating') === false) return false;
// First we'll need to create a fresh query instance and touch the creation and
// update timestamps on this model, which are maintained by us for developer
// convenience. After, we will just continue saving these model instances.
if ($this->timestamps)
{
$this->updateTimestamps();
}
// If the model has an incrementing key, we can use the "insertGetId" method on
// the query builder, which will give us back the final inserted ID for this
// table from the database. Not all tables have to be incrementing though.
$attributes = $this->attributes;
if ($this->incrementing)
{
$this->insertAndSetId($query, $attributes);
}
// If the table is not incrementing we'll simply insert this attributes as they
// are, as this attributes arrays must contain an "id" column already placed
// there by the developer as the manually determined key for these models.
else
{
$query->insert($attributes);
}
// We will go ahead and set the exists property to true, so that it is set when
// the created event is fired, just in case the developer tries to update it
// during the event. This will allow them to do so and run an update here.
$this->exists = true;
$this->fireModelEvent('created', false);
return true;
}
For me, I had to set protect $primaryKey to the column name of the primary key in the model to solve the issue. (skill_id was the column name so in the Skill model I set protected $primaryKey = 'skill_id', default is 'id'.)