I have a comments table that have comments of articles, recipes and products. So its a polymorphic relation. I have two columns rel_id and rel_type in my comments table those are being used for this relation.
Now in my Comment.php I have following relation
public function rel()
{
$this->morphTo();
}
And in my other all classes I have following
public function comments()
{
return $this->morphMany('App\Models\Comment', 'rel');
}
When I try to get owner of comment and all its related data I found class not found error. For example
$comments = Comment::find(1);
echo $comments->rel_type //article
Now if I want to get data of article and when I try
$comments->rel
I found article class not found. I am using namespace App\Models\Article I have searched it out I found answer given here. When I try accepted answer, nothing happens, error remains same. When I try second answer of same question, I found
Relationship method must return an object of type Illuminate\Database\Eloquent\Relations\Relation
My ultimate goal is to get comment owner data like $comments->articles->id and so on. Please guide how can I do that?
I have a blog post about this:
http://andrew.cool/blog/61/Morph-relationships-with-namespaces
There are a couple things you need to do. First, for all of your models that have comments, add the $morphClass variable to the class, e.g.:
class Photo {
protected $morphClass = 'photo';
}
class Album {
protected $morphClass = 'album';
}
Second, on the Comment class, define an array called $rel_types on your Comment class. This will be basically the inverse of what you just did, it's a mapping from short name to full class name.
class Comment {
protected $rel_types = [
'album' => \App\Album::class,
'photo' => \App\Photo::class,
];
}
Finally, define an accessor for the rel_type column. This accessor will first retrieve the column from the database ("album", "photo", etc.) and then convert it to the full class name ("\App\Album", "\App\Photo", etc.)
/**
* #param string $type short name
* #return string full class name
*/
public function getRelTypeAttribute($type)
{
if ($type === null) {
return null;
}
$type = strtolower($type);
return array_get($this->rel_types, $type, $type);
}
Note: $morphClass is something Laravel actually defines, so it has to be named that. $rel_types can be named anything you want, I just based it off of the rel_type column you have.
To make this even better, add that getRelTypeAttribute method to a trait so that any model that morphs can reuse that trait and method.
Related
Lets assume I have a table posts with the fields id and content and published.
A User can have multiple posts, and a post can belong to multiple pages and there might be a lot more relations to a post.
Lets say we have an admin that wants to moderate the posts, the posts should only be visible if approved. So I add the boolean published where posts that are not published 0 should never be visible (only in specific cases e.g. to moderate the post).
Is it possible to set something in the Post model to restrict the related models from loading non published posts.
I want to avoid that I have to filter in the relation, e.g. if I call $user->posts I do not want to check if the posts are published, the non published results should not be available only if i do a search like. Post::where('published','0'). Basically something like softdeletes but than with a custom field.
An example, where the opposite relations are also defined, to make it easier to understand would be:
class Post extends Model
{
use SoftDeletes;
protected $table = 'posts';
public function collection()
{
return $this->belongsTo('App\Collection');
}
public function style()
{
return $this->belongsTo('App\Style');
}
public function pictures()
{
return $this->hasMany('App\Picture')->orderBy('priority', 'asc');
}
public function user()
{
return $this->belongsTo('App\User');
}
}
You can use global query scope in the Model as below to add your desired filters to each query, like below:
// Post.php
class Post extend Model
{
protected static function booted()
{
static::addGlobalScope('published', function (Builder $builder) {
$builder->where('published', true);
});
}
// ...
}
Whenever you don't want to apply the global query scope, use withoutGlobalScope with the name of the query scope, like below:
Post::withoutGlobalScope('published')->get();
I have created one and I thought it works:
<?php
namespace App\Traits;
use Carbon\Carbon;
trait FormatDates
{
public function setAttribute($key, $value)
{
parent::setAttribute($key, $value);
if (strtotime($value))
$this->attributes[$key] = Carbon::parse($value);
}
}
But there is a problem when calling related models. For example if you have an Article and Tag model and you want to get all tags like this:
$article->tags
it returns null because of that getter mutator.
How to fix this?
update 17.11.2017
I have found a solution to my problem. The best way to present the date in locale is to use this function:
\Carbon\Carbon::setToStringFormat("d.m.Y H:i");
simply create a service provider or a middleware and it will show all $dates in format you want. There is no need to make a getter.
Based from this: https://laravel.com/api/5.5/Illuminate/Database/Eloquent/Concerns/HasAttributes.html#method_getAttribute
The description says:
Get a plain attribute (not a relationship).
Luckily there are another two methods below it called getRelationValue and getRelationshipFromMethod, and it reads:
Get a relationship.
Get a relationship value from a method.
respectively.
And in your example, it looks like you're calling a relation.
I think you should consider it when doing your universal getter/mutator.
UPDATE:
If you inspect the code, the getAttribute also calls the getRelationValue method. But it is the last resort of the function; if the key is neither an attribute or has a mutator or is a method of the class.
Here is the stub: https://github.com/laravel/framework/blob/5.5/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php#L302
/**
* Get an attribute from the model.
*
* #param string $key
* #return mixed
*/
public function getAttribute($key)
{
if (! $key) {
return;
}
// If the attribute exists in the attribute array or has a "get" mutator we will
// get the attribute's value. Otherwise, we will proceed as if the developers
// are asking for a relationship's value. This covers both types of values.
if (array_key_exists($key, $this->attributes) ||
$this->hasGetMutator($key)) {
return $this->getAttributeValue($key);
}
// Here we will determine if the model base class itself contains this given key
// since we don't want to treat any of those methods as relationships because
// they are all intended as helper methods and none of these are relations.
if (method_exists(self::class, $key)) {
return;
}
return $this->getRelationValue($key);
}
ANOTHER UPDATE
Since you've changed your question:
You can just put the attribute name to $casts or $dates array (in your Model) so Laravel will automatically transform it into a Carbon instance when accessing it, like this:
class Article extends Model {
...
protected $dates = ['some_date_attribute`];
or with $casts
...
protected $casts = ['some_date_attributes' => 'date'];
You really can avoid this, it's already there!
on the model Class you can do:
protected $dates = ['nameOfTheDateOrTimestampTypeField','nameOfAnotherOne'];
I have two models, one is LeadHistory and the other one is Leads.
Leads:
class Leads extends Model
{
public function lead_history()
{
return $this->hasMany('App\LeadHistory');
}
}
LeadHistory:
class LeadHistory extends Model
{
public function lead()
{
return $this->belongsTo('App\Leads', 'lead_id', 'id');
}
}
When I go into php tinker, get the first Lead ($lead = App\Leads::first();), create a new LeadHistory ($leadHistory = new App\LeadHistory;) and ($leadHistory->message = 'second one';) and ($leadHistory->status_id = 11;) then try to save the leadHistory ($leadHistory->lead()->save($lead);). I get this error message:
BadMethodCallException with message 'Call to undefined method Illuminate\Database\Query\Builder::save()'
Can someone point me in the right direction, I feel like I have been following the instructions given in Laracasts but can't seem to get the LeadHistory to save with the associated Lead ID.
You’re trying to call save() on a relation rather than a model I think.
Instead, “attach” your LeadHistory model to your Lead model:
$lead = Lead::create($leadAttributes);
$history = new LeadHistory($leadHistoryAttributes);
$lead->history()->attach($history);
You’ll need to rename your relation if you copy-and-paste the above code:
class Lead extends Model
{
public function history()
{
return $this->hasMany(LeadHistory::class);
}
}
I feel the name “lead history” is superfluous when you’re already working with a Lead model.
Try to save $leadHistory first:
$leadHistory->save();
And then:
$lead->lead_history()->save($leadHistory)
Correct me if I'm wrong, but since you already have a model instance of your target App\Leads, I think you should be able to simply access the id of that instance and inject it into a static create call:
$lead = App\Leads::first();
$leadHistory = App\LeadHistory::create([
'message' => 'second one',
'status_id' => 11,
'lead_id' => $lead->id
]);
Before being able to use the create method you'd have to make the properties you want to assign 'mass assignable', by defining a protected property called $fillable in your model:
class LeadHistory extends Model
{
protected $fillable = [
'message',
'status_id',
'lead_id'
];
public function lead()
{
return $this->belongsTo('App\Leads', 'lead_id', 'id');
}
}
This will effectively associate your new record with that lead, since the only thing the Eloquent model does in this regard is providing another way to describe the same relationships your database exercises.
Some other answers mention the attach() method of an Eloquent model. This method is used to attach two models with a many to many relationship (relationships defined with belongsToMany).
I need a little clarification.
This error message "Trying to get property of non-object" to what is referred to when using Eloquent? I'm going crazy. I am using a function:
public function squadra_post()
{
return $this->hasMany('Squadra','id_squadra');
}
that is the model that extends Eloquent Post. When the call:
<?php $squadra = Post::find($roles->id_squadra)->squadra_post; ?>
gives me the error that I mentioned before.
EDIT
his is the class post with their methods. What to give problems is "squadra_post ()" I need to extract the name of the team by id_squadra post content in table.
class Post extends Eloquent implements UserInterface, RemindableInterface
{
protected $table = 'post';
protected $primaryKey = 'id_post';
public $timestamps = false;
public function commenti()
{
return $this->hasMany('Commento','id_post');
}
public function apprezzamenti()
{
return $this->hasMany('Apprezzamenti','id_post');
}
public function squadra_post()
{
return $this->hasMany('Squadra','id_squadra');
}
}
This is the code of the class "squadra"
class Squadra extends Eloquent implements UserInterface, RemindableInterface
{
protected $table = 'squadra';
protected $primaryKey = 'id_squadra';
public $timestamps = false;
}
And finally, this is the code that gives me problems. That is what gives me the error: Trying to get property of non-object
#foreach($post['post'] as $roles)
#if($roles->id_squadra != 0)
$squadra = Post::find($roles->id_squadra)->squadra_post;
#foreach($squadra as $squadra_post)
{{ $squadra->nome }}
#endforeach
#endif
#endforeach
When you tired attempting to find the particular post, it couldn't find it and hence it is throwing an exception saying "Trying to get property of non-object". If it had found the post, it would have returned the squadra_post and it would have been stored in the variable.
But instead it could not find that particular and could not construct an object with it and hence it could not find that particular property name squadra_post. Therefore the error "Trying to get property of non-object".
You could handle this exception by putting the code in a try and catch block.
In the line
$squadra = Post::find($roles->id_squadra)->squadra_post;
the squadra_post() method
returns a collection of objects (posts I guess) and not an object because of the nature of your relationship (1 to many) while Post::find() returns the object. I think this is where the error occurs.
try the following code
$squadra = Post::find($roles->id_squadra);
if ($squadra) {
echo $squadra->name;
foreach ($squadra->squadra_post as $post) {
echo $post->title;
echo $post->content; //etc
}
}
EDIT
Also you'll have to be sure that the foreign key is the id_squadra that you declare in your squadra_post() method.
First off, your relation won't work this way, becuase Laravel 4+ expects relation names to be camelCased and only then you can use dynamic properties.
So rename it to squadraPost()and call like $post->squadraPost, and in case of hasMany you will get a Collection here, no matter if there is any related model or not.
Next thing is checking if related model exists - for this read the answer here: Laravel check if related model exists
And last, but not least: you may get null from find() method, so first make sure this:
$post = Post::find(..)
returns your model, not null. Only then can you call its relation:
if ($post) $post->squadraPost;
Is there a way that I can get the current database table in use by the model that I'm in? I see that there is a table() function in Laravel/Database/Eloquent/model.php but I've been unsuccessful calling it calling it from the model that I'm in.
There is a public getTable() method defined in Eloquent\Model so you should be able to use $model->getTable().
Taylor has an answer to your question:
Within the model class you can do something like this:
return with(new static)->getTable();
If you want all your models to have the ability to return table name statically, then so something like this:
class BaseModel extends Eloquent {
public static function getTableName()
{
return with(new static)->getTable();
}
}
class User extends BaseModel {
}
User::getTableName();
Edit April 2019: This answer is now out of date. See the new correct answer by Flyn San
Yes - Eloquent has a $table variable. There are two ways you can access this:
class yourModel extends Eloquent {
public static $table = "differentTable";
function someFunction()
{
return yourModel::$table;
}
}
or
class yourModel extends Eloquent {
public function someFunction()
{
return $this->table();
}
}
then in your code
Route::get('/', function () {
$model = new yourModel();
dd($model->someFunction());
});
In my case, i'm using laravel 5.4
return (new static)->getTable();
Since table is a protected property in the Model class (Laravel >= 5) you will need an instance of your Model.
Here is a case example:
DB::table( (new YourModelClassname)->getTable() )
->update(['field' => false]);
You can get name of a model's table by following code:
If we have a Model as ModelName:
ModelName::query()->getQuery()->from
This method also works fine in case of custom table name that are defined by protected $table = 'custom_table_name' in the Model.
It will return the table name from the model. perfectly worked on laravel 8
app(Modelname::class)->getTable()
you have to replace Modelname with your model class
Based on Lucky Soni answer, there is another easy trick if you want to directly call it from Vontroller or View.
Tested in Laravel 6, and I keep using it, if you are "One Line Programmer" who hates extra line instance declaration. No need for extra lines in Model file too.
$string_table_name = with(new \App\Model\TableModelName)->getTable();
or better you may also be able to just call this
$string_table_name = (new \App\Model\TableModelName)->getTable();
It will return plain string of the tabel name even if you rename $table variable inside model class.
EDIT :
Minus Rep ?? Maybe you should try this first in your controller instead making new function in model class just to get table name and no need to declare the object when calling.
with() itself is Laravel helper function that returns an object of the class. and inside class that extends Model, already has function getTable(). So, you don't have to put another new redundant function inside model class.
It seems the latest version, you can just call (new Class) without with() function.
The difference between this answer and Lucky's answer, mine doesn't make any new function inside Model class to get the table name, even you can just call the function inside the Controller and View without declaring the object of model class. It's for beautify the code.
While Lucky's answer create new function that inside Model class, and you need to call the function from the object.
Simple way to get table name from Laravel Model by this:
$tableName = app(\App\User::class)->getTable();
Don't forget to replace:
\App\User
With Model path.
Here's an other approach so that you can get a model's table name statically.
Define a Trait: app/Traits/CanGetTableNameStatically.php
<?php namespace App\Traits;
trait CanGetTableNameStatically
{
public static function tableName()
{
return (new static)->getTable();
}
}
Extend your required Model or BaseModel with the use statement.
app/Models/BaseModel.php
<?php namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use App\Traits\CanGetTableNameStatically;
class BaseModel extends Model
{
use CanGetTableNameStatically;
// ...
}
On your models, if you set the custom table name on Laravel's reserved attribute: protected $table then it will still work & return correct table name.
app/Models/Customer.php
<?php namespace App\Models\Master;
use App\Models\BaseModel;
class Customer extends BaseModel
{
protected $table = 'my_customers';
// ...
}
Usage: just call YourModel::tableName() anywhere.
In Views:
{{ \App\Models\Customer::tableName() }}
When doing Joins:
DB::table( Product::tableName() . ' AS p' )
->leftJoin( ProductCategory::tableName() . ' AS pc', 'pc.id', '=', 'p.category_id')
// ... etc
Note:
I use this approach where needed but full disclosure, I found another answer here that have the exact same approach, so I copy pasted here for reference of course with citation thanks to #topher
Based on tailor Otwell's answer you could use something like this:
with(new Model)->getTable();
Note: tested on versions 5.x, 6.x, 7.x, 8.x and it works well.
another solution is to use the resolve helper like so:
resolve('\\App\\Models\\User')->getTable()
None of the answers so far will get you the table name with the prefix, if you are using a table name prefix. At this time it seems like we need to concatenate the prefix with the table name ourselves if we want the real name of database table.
Here's how to get the table name including the table prefix:
echo \App\MyModel::query()->getQuery()->getGrammar()->getTablePrefix() . app(\App\MyModel::class)->getTable();
in laravel 7.x (i'm used)
you can get table name with (new Target())->getTable();
$query->where('parent_id', function ($query) use ($request) {
$query->select('id')->from((new Target())->getTable())->where('unit_id', $request->unit_id);
});
hope it's helps
To people who want to get table name from a Builder object instead of other object, here you are:
$conn = DB::connection("my_private_mysql_conn");
$my_builder_object = $conn->table("my_table_name");
//This will print out the table name
print $my_builder_object->from;
It will work 100%. You will get table name.
$object = new OrderStockProduct();
// Use below line only when you have dynamic connection in laravel project
// $object->setConnection('mysql');
$object = $object->getTable();
dd($object);
I just wanted to add the following for people coming from search engines:
In case you do not even want to instantiate the Model at all (faster?) :
$model = 'App\User';
$modelTable = str_replace('\\', '', Str::snake(Str::plural(class_basename($model))));
dd($modelTable); // will return "users"
That might look ugly but that's exactly how the getTable() method resolves it under the hood, so...
You will need to use Illuminate\Support\Str; on top of your file.
Addendum: implying you follow the framework's standards (i.e: Post model has posts table, User model has users table, etc)
In Laravel 4 use static method
$table_name = Model::getTable();
or "self" inside Eloquent Model
$table_name = self::getTable();