Loading model objects in Laravel when passing primarykey in new() - laravel

I'm beginning to play around with Eloquent models. I've got the following model:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
/**
* Class Character
* #package App
*
* #property string $name
* #property int $id
* #property int $score
*/
class Character extends Model
{
protected $table = 'characters';
public function __construct(array $attributes = [])
{
parent::__construct($attributes);
}
}
My question is, is it possible when I do the following:
$character = new Character( 1 ); // 1 is the primary key
I'd like it to go ahead and load the model with the info in the database. If it doesn't exist, I'd like to set 1 as $character->id. Is this possible? Or does Eloquent already do this and I'm just missing something?

Theoretically you can do it by this:
public function __construct($primaryKey)
{
parent::__construct($attributes);
$character = self::find($primaryKey);
if(is_null($character)){
$character = self::find(1);
}
return $character;
}
But, ideally you should not do this because Laravel is not designed this way. You better do the same thing outside of the model. Besides, there is another problem in this code, we are hard coding the $primaryKey = 1 if the object is not found. Such hard coded value can be risky sometimes.

Don't do this. Eloquent is not designed to work this way. Overriding the constructor like this is bound to cause plenty of issues down the road.
Consider adding a static method for your use-case - much like Imran's solution above. That way you can enjoy the odd behaviour that you're after without risking breaking anything else.

The closest solution you can get is using findOrNew() function https://laravel.com/api/5.2/Illuminate/Database/Eloquent/Builder.html#method_findOrNew.
You can use it as
$character = Character::findOrNew(1);
This will query the characters table for id 1 and will return an instance of the Character model, if the id is not found in the database a new instance of Character model will be instantiated and returned. However there is no option for setting the id of the model.

Related

How do i switch a custom implementation of a interface on a condition in http controller in Laravel

I have a method in my controller which basically is just storing a form to the database.
My form is very big and it has more than 30-40 fields.
So i need to store this information in different 3 tables on a condition.
For example :
foreach($request->all() as $answer):
if($answer->employeeType === 1){
//store data to type_one_table
} else if($answer->employeeType === 2){
//store data to type_two_table
} else if($answer->employeeType === 3){
//store data to type_two_table
} else {
//store data to some_other_table
}
endforeach;
So i was thinking to use some custom StoreInterface which will have some store method.
and extract above logic to its own implementation.
And then in my Controllers constructor receive that interface instance.
But how i can call which implementation do i need for particular condition.
Can i do this a controller ?
Or Should i use different strategy in this case.
please guide me.
Thanks.
You can have a general interface like this:
interface EmployeeStoreContract
{
public function saveAnswer();
}
Then implement this for all of the different ways you can save the answer, e.g.:
class FirstTypeEmployee implements EmployeeStoreContract
{
public function saveAnswer()
{
// do something
}
}
And in your controller to figure out how to save the request data:
public function store()
{
$employeeTypesMap = [
1 => 'FirstTypeEmployee',
2 => 'SecondTypeEmployee',
3 => 'ThirdTypeEmployee',
];
foreach ($request->all() as $answer) {
$employeeType = $answer->employeeType;
if (!array_key_exists($employeeType, $employeeTypesMap)) {
throw new \InvalidArgumentException('Answer type is not available.');
}
$employeeStrategyClass = "App\\Service\\Employee\\{$employeeTypesMap[$employeeType]}";
$employeeStrategyObject = new $employeeStrategyClass;
$employeeStrategyObject->saveAnswer($answer);
}
}
Please note the namespace where you have these classes App\Service\Employee, could be anything you want. Also feel free to make an abstract class or a trait to reuse some of the functionality of each employee class.
There can be many approaches.
I will suggest 2 ways that i would personally use:
1) Create dedicated eloquent models one for each case.
For example:
if($answer->employeeType === 1){
EmployeeOne::create($answer);
}
And your eloquent model can look like:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use DB;
class EmployeeOne extends Model
{
protected $table = 'type_one_table';
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = [
// your db field names
];
/**
* The attributes that should be hidden for arrays.
*
* #var array
*/
protected $hidden = [
];
public $timestamps = false;
}
Make sure that the array keys of the array you pass to the create function must match your db fields. I just gave you an example of the first if so you can take it from here.
2)
Another approach would be to create a model that accepts as a parameter the table name. So based on your if you are going to pass the table name you want to insert and the data to be inserted.
if($answer->employeeType === 1){
$this->myModel->insertData($answer,'type_one_table');
}
And in your model you are going to have something like:
public function insertData($answer,$table){
$query = DB::table($table)->insertGetId($answer); // returns the id of the new record
return $query;
}
So in every if-else statement you just change the table name passing to your model function and that's it.

How to create a universal getter/mutator for datetimes in Laravel?

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'];

Get data from polymorphic relations with namespaces

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.

Laravel 5.1 Reusable Slug

Hello how can I make the unique slug logic reusable in my project. I have a method for creating a unique slug in my Product model and I want to use that same logic in my other model here:
/**
* Set the name attribute and automatically the slug
*
* #param string $name
*/
public function setNameAttribute($name)
{
$this->attributes['name'] = $name;
if(! $this->exists)
{
$this->setUniqueSlug($name, '');
}
}
/**
* Recursive routine to set a unique slug
*
* #param string $name
* #param mixed $extra
*/
public function setUniqueSlug($name, $extra)
{
$slug = str_slug($name . '-' . $extra);
if (static::whereSlug($slug)->exists())
{
$this->setUniqueSlug($name, $extra + 1);
return;
}
$this->attributes['slug'] = $slug;
}
Martin Beans comments would be the best way to go about this.
There is nothing wrong with Paul Vidal's way, however, I wouldn't use the Base Model approach unless you're going to use it with every model.
As long as you're not using different field names for the slug and you're using something like a parent slug (i.e. product/{slug}) you should be able to put the above code straight into a trait and then use in on the models you want to.
Hope this helps!
There are many ways, you can create a Base model class that extends from eloquent, and then extends the rest of your models from that base model. Create the method "setUniqueSlug" in that base model to be accesible from any other.
Or you can create a Helper class, so you can call "setUniqueSlug" from anywhere.

How to return database table name in Laravel

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();

Resources