Set new method for all eloquent models in Laravel - laravel

I'm alway doing the same thing with some of my models. I get list of all models as [id => name] array. I need that to display them in drop-down list in other's models crud to set relations.
How can I put one static method in one place that would be available from any model?
Now I have to write this code everywhere:
public static function getModelNameList()
{
return self::select('id','name')->get()->mapWithKeys(function ($model){
return [$model->id => $model->name];
})->toArray();
}
and then
$list = ModelName::getModelNameList();

You can use
$list = ModelName::pluck('name', 'id'); //Collection
$list = ModelName::pluck('name', 'id')->all(); //array
$list = ModelName::pluck('name', 'id')->toArray(); //array
You can define BaseModel
use Illuminate\Database\Eloquent\Model;
class BaseModel extends Model
{
public static function getModelNameList()
{
return self::select('id','name')->get()->mapWithKeys(function ($model){
return [$model->id => $model->name];
})->toArray();
}
}
Then extend BaseModel
use App\Models\BaseModel;
class ModelName extends BaseModel
{
}
Or you can define Trait and use in model

Related

Laravel : How to Pass an attribute to Eloquent model constructor

I want to use strtolower() before saving data in database for 5 attributes,
I'm using this code in Model
public function setFirstNameAttribute($value)
{
$this->attributes['firstName'] = strtolower($value);
}
public function setLastNameAttribute($value)
{
$this->attributes['lastName'] = strtolower($value);
}
public function setUserNameAttribute($value)
{
$this->attributes['userName'] = strtolower($value);
}
... etc
Can I use the __construct method instead of the above code?
There are two ways first one, to use boot method directly (preferred for small changes in model like in your question)
Method 1 :
we can directly use the boot method,
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Mymodel extends Model
{
public static function boot()
{
parent::boot();
static::saving(function ($model) {
// Remember that $model here is an instance of MyModel
$model->firstName = strtolower($model->firstName);
$model->lastName = strtolower($model->lastName);
$model->userName = strtolower($model->userName);
// ...... other attributes
});
}
}
Method 2 :
So we can use here a simple trait with a simple method for generating a strtolower() for a string.This is preferred when you have to do bigger changes in your model while performing operations in model like saving, creating etc. Or even if you want to use the same property in multiple models.
Create a trait MyStrtolower
<?php
namespace App\Traits;
trait MyStrtolower
{
public function mystrtolower($string)
{
return strtolower($string);
}
}
We can now attach this trait to any class that we want to have the mystrtolower method.
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
use App\Traits\MyStrtolower;
class Mymodel extends Model
{
use MyStrtolower; // Attach the MyStrtolower trait to the model
public static function boot()
{
parent::boot();
static::saving(function ($model) {
// Remember that $model here is an instance of MyModel
$model->firstName = $model->mystrtolower($model->firstName);
$model->lastName = $model->mystrtolower($model->lastName);
$model->userName = $model->mystrtolower($model->userName);
// ...... other attributes
});
}
}
If you want to not repeat all these lines of code for every model you make, make the trait configurable using abstract methods so that you can dynamically pass the attribute names for which you want to lower case string, like employee_name is Employee Model and user_name in User Model.

Laravel: Creating a common function for all models

In my laravel(7.x) application, I have a common functionality to show the count of all the active and inactive records in all the modules. Therefore, I am obligated to repeat the same functionality on every module.
For example: Device, DeviceType, DeviceCompany, etc models have a same method called _getTotal and everywhere that _getTotal method is doing the same work.
Device.php
class Device extends Model
{
protected $table = 'devices';
...
public function _getTotal($status = \Common::STATUS_ACTIVE)
{
return self::where([
'status' => $status
])->count() ?? 0;
}
}
DeviceType.php
class DeviceType extends Model
{
protected $table = 'device_types';
...
public function _getTotal($status = \Common::STATUS_ACTIVE)
{
return self::where([
'status' => $status
])->count() ?? 0;
}
}
I tried to put this method in the Base Model but I think may not be a good practice. Am I right..?
Is there any way to make this method _getTotal a common method for all the modules..?
You could move this method to a trait and include the trait instead to all classes that need this method.
trait DeviceStatusTotal
{
public function _getTotal($status = \Common::STATUS_ACTIVE)
{
return self::where([
'status' => $status
])->count() ?? 0;
}
}
DeviceType.php
class DeviceType extends Model
{
use DeviceStatusTotal;
protected $table = 'device_types';
// ...
}
Or you can create a classe extending Model default class and your models extends from this custom class (that haves your custom function)
You can use laravel Global scopes:
https://laravel.com/docs/5.7/eloquent#global-scopes
Another why use traits and use in the models and make the method to local scope as:
public function scopePopular($query) {
return $query->where('votes', '>', 100);
}

Where is the create method of BelongsTo class?

I have a general class for uploads.
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class File extends Model
{
protected $guarded = ['id'];
}
An ID of File model will be specified on each person to serve as the avatar:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Person extends Model
{
public function avatar()
{
return $this->belongsTo('App\File');
}
public function putAvatar($file)
{
$path = $file->store('avatars');
// This would work if `avatar()` was a `hasMany()` relation
$this->avatar()->create([
'path' => $path,
]);
}
}
This doesn't work exactly as intended, but it creates the File model in database. Why?
The $this->avatar() is an instance of BelongsTo and there is no create method. I checked the class, the included traits and the Relation class that it extends. Reference here.
So what's going on, where is the code that creates the new model?
I tried using a ReflectionMethod but while $this->avatar()->create() works, new ReflectionMethod($this->avatar(), 'create') returns a ReflectionException with message Method Illuminate\Database\Eloquent\Relations\BelongsTo::create() does not exist.
There is no method for saving entities on belongsTo relationships. Once the entity is created, you can associate it with the model.
$avatar = File::create([...]);
$this->avatar()->associate($avatar)->save();
To allow querying of relationships, undefined method calls are passed to an Eloquent Builder instance which does have a create method.
All relationships extend the Relation class which defines:
public function __call($method, $parameters)
{
if (static::hasMacro($method)) {
return $this->macroCall($method, $parameters);
}
$result = $this->query->{$method}(...$parameters);
if ($result === $this->query) {
return $this;
}
return $result;
}
The method used for belongsTo() should be save(), not created(). Make sure to pass as an argument your File class:
$this->avatar()->save(new File([
'path' => $path,
]));

Laravel - how to makeVisible an attribute in a Laravel relation?

I use in my model code to get a relation
class User extends Authenticatable
{
// ...
public function extensions()
{
return $this->belongsToMany(Extension::class, 'v_extension_users', 'user_uuid', 'extension_uuid');
}
// ...
}
The Extension has field password hidden.
class Extension extends Model
{
// ...
protected $hidden = [
'password',
];
// ...
}
Under some circumstances I want to makeVisible the password field.
How can I achieve this?
->makeVisible([...]) should work:
$model = \Model::first();
$model->makeVisible(['password']);
$models = \Model::get();
$models = $models->each(function ($i, $k) {
$i->makeVisible(['password']);
});
// belongs to many / has many
$related = $parent->relation->each(function ($i, $k) {
$i->makeVisible(['password']);
});
// belongs to many / has many - with loading
$related = $parent->relation()->get()->each(function ($i, $k) {
$i->makeVisible(['password']);
});
Well, I got the idea from https://stackoverflow.com/a/38297876/518704
Since my relation model Extension::class is called by name in my code return $this->belongsToMany(Extension::class,... I cannot even pass parameter to it's constructor.
So to pass something to the constructor I may use static class variables.
So in my Extension model I add static variables and run makeVisible method.
Later I destruct the variables to be sure next calls and instances use default model settings.
I moved this to a trait, but here I show at my model example.
class Extension extends Model
{
public static $staticMakeVisible;
public function __construct($attributes = array())
{
parent::__construct($attributes);
if (isset(self::$staticMakeVisible)){
$this->makeVisible(self::$staticMakeVisible);
}
}
.....
public function __destruct()
{
self::$staticMakeVisible = null;
}
}
And in my relation I use something like this
class User extends Authenticatable
{
...
public function extensions()
{
$class = Extension::class;
$class::$staticMakeVisible = ['password'];
return $this->belongsToMany(Extension::class, 'v_extension_users', 'user_uuid', 'extension_uuid');
}
...
}
The highest voted answer didn't seem to work for me (the relations attribute seems to be a protected array now so can't be used as a collection in #DevK's answer), I instead used:
$parent->setRelation('child', $parent->child->first()->setVisible(['id']));

Laravel Mutators with Constructor?

Ill have a problem because my mutators never get called when ill use an constructor:
Like this:
function __construct() {
$this->attributes['guid'] = Uuid::generate(4)->string;
}
public function setDateAttribute($date) {
dd($date); // Never gets called
}
Ill already found out, that the mutators would ne be called when ill use an constructor, so i should use:
public function __construct(array $attributes = array()){
parent::__construct($attributes);
$this->attributes['guid'] = Uuid::generate(4)->string;
}
public function setDateAttribute($date) {
dd($date); // now its getting called
}
But so ill get the following error:
array_key_exists() expects parameter 2 to be array, null given
But i dont know where? Can anyone help me out how to create a default value (like a UUID) for a specific column, and use mutators in the same class?
Edit: Thanks Martin Bean for your help, but i am now getting the following error:
Cannot declare class App\Uuid because the name is already in use
I have tried:
Creating a File called "Uuid.php" in /app/ -> /app/Uuid.php
With this content:
<?php namespace App;
use Webpatser\Uuid\Uuid;
trait Uuid
{
public static function bootUuid()
{
static::creating(function ($model) {
$model->uuid = Uuid::generate(4)->string();
});
}
}
Changed my Model to:
<?php namespace App;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Model;
class Task extends Model {
use \App\Uuid;
Thank you very much!
Edit 2:
Ill tried it this way:
class Task extends Model {
protected $table = 'tasks';
protected $fillable = ['..... 'date', 'guid'];
public function setGuidAttribute($first=false){
if($first) $this->attributes['guid'] = Uuid::generate(4)->string;
}
TaskController:
public function store() {
$input = Request::all();
$input['guid'] = true;
Task::create($input);
return redirect('/');
}
Works fine, but when ill use:
public function setDateAttribute(){
$this->attributes['date'] = date('Y-m-d', $date);
}
In Task.php ill get:
Undefined variable: date
EDITED:
based on your comment:
i would like to set a field on first insert
use Uuid; //please reference the correct namespace to Uuid
class User extends Model{
protected $fillable = [
'first_name',
'email',
'guid' //add guid to list of your fillables
]
public function setGuidAttribute($first=false){
if($first) $this->attributes['guid'] = Uuid::generate(4)->string;
}
}
Later:
$user = User::create([
'guid' => true, //setAttribute will handle this
'first_name' => 'Digitlimit',
'email" => my#email.com
]);
dd($user->guid);
NB: Remove the __construct() method from your model
Mutators are called when you try and set a property on the model—they’re invoked via the __get magic method. If you manually assign a property in a method or constructor, then no mutators will ever be called.
Regardless, you should not be creating constructors on Eloquent model classes. This could interfere with how Eloquent models are “booted”.
If you need to set an UUID on a model then I’d suggest using a trait that has its own boot method:
namespace App;
trait Uuid
{
public static function bootUuid()
{
static::creating(function ($model) {
$model->uuid = \Vendor\Uuid::generate(4)->string();
});
}
}
You apply the trait to your model…
class SomeModel extends Model
{
use \App\Uuid;
}
…and now each time a model is created, a UUID will be generated and stored in the database with your model.

Resources