How to get name of primary key from model in laravel?
like this
dd(User::primaryKeyName()); // -> 'user_id'
I want to sort data on primary if 'order' is empty
$data = User::orderBy(Input::get('order', User::primaryKey()), 'ASC')->get();
but have
Non-static method Illuminate\Database\Eloquent\Model::getKeyName() should not be called statically, assuming $this from incompatible context
That method tries to get the value from a protected class property return $this->primaryKey; and $this needs a context from a class instance. If you really need to get that name dynamically, you could do this:
App::make('User')->getKeyName();
So your code would look something like this:
$data = User::orderBy(Input::get('order', App::make('User')->getKeyName()), 'ASC')->get();
The fastest way to do this is probably like so:
(new User)->getKeyName();
(new User)->getTable();
However, I think the two approaches below provide a cleaner interface to the method.
The first approach uses a static method on your base model class:
class BaseModel extends Model
{
protected static function KeyName() {
return (new static)->getKeyName();
}
protected static function TableName() {
return (new static)->getTable();
}
}
Then inherit from this class in your model:
class ModelName extends BaseModel
{
...
}
And use like this: ModelName::KeyName();
If you don't have access to a base model class you can also use a trait:
trait EloquentGetTableNameTrait
{
public static function TableName()
{
return (new static)->getTable();
}
}
Which you use like this in your model:
class ModelName
{
use EloquentGetTableNameTrait;
...
}
Unfortunately, all these approaches require instantiating an instance of the model, but there is currently no other way to retrieve this value.
Reference: https://github.com/laravel/framework/issues/1436
Related
I know this might seem anti pattern, and a lot will throw stones at me, but please hear me out.
I want to create a generic Controller to support many reference tables (mostly id, label). So I did something like this:
class GenericController extends Controller
{
public function index($modelName)
{
$x = '\\App\\Models\\'.$modelName;
$data = $model->all();
return view('generic.list', ['model'=>$model, 'data'=>$data]);
}
}
And this way my routes in web.php will be reduced to the minimum like this:
//List
Route::get('/{model}', function ($model) {
return App::call('\App\Http\Controllers\GenericController#index', ['modelName' => $model]);
});
It's working very well with simple CRUD actions like store, update, etc.. However I know I am over simplifying the design because sometimes I need to return a field from a joined table in the index list for example. That's where I am heading into a dead end, sort of.
My first thought was to create a controller for each model that inherits from the GenericController like this:
class CategoryController extends GenericController
{
}
And whenever I need to override the GenericController method, I would simply add it to the child class. However how can I do this from inside the GenericController (call a method in a sub class from parent class)? Because otherwise I will have to create routes for every single model which is against my wish.
So basically I am looking for something like this:
class GenericController extends Controller
{
public function index($modelName)
{
$x = '\\App\\Models\\'.$modelName;
//this thing I'm looking for is something like this:
//Check if we have CategoryController and it has a definition for index
//if yes do something like $data = CategoryController->index();
//otherwise just call $data = $model->all();
return view('generic.list', ['model'=>$model, 'data'=>$data]);
}
}
So I know this seems weird and anti-pattern, but other wise how can I create my generic routes and controller actions?
You are right, this is not really what is called "best practice". However, from a POO standpoint, it is an interesting question.
This what you can do:
class GenericController extends Controller
{
protected function getData(string $model)
{
return $model::all();
}
public function index($modelName)
{
$model = '\\App\\Models\\'.$modelName;
$data = $this->getData($model);
return view('generic.list', ['model'=>$model, 'data'=>$data]);
}
}
By default, the data will be retrieved "the simple way", using $data = $this->getData($model);.
However, if you make a CategoryController:
class CategoryController extends GenericController
{
protected function getData(string $model)
{
return Category::query()->with('something')->where('hello','world')->get();
}
}
You will just have to override the getData method inside your CategoryController.
This is the way to go if you want something clean. Of course, your categories routes will have to use this CategoryController instead of the GenericController.
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.
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);
}
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.
I have one question, that seems to be logical, but I can't find answer for it.
Let's say I have Model Task:
class Task extends Eloquent {
protected $fillable = array('is_done');
}
So, I have one property is_done, but when working on frontend and backend part of application, I would like to have isDone as model property.
Is there a way to say it to framework, to somehow repack it for me? So that I am able to use isDone, throughout application, and that Model takes care of converting it to is_done, when it comes to saving/updating.
This would help me, so I don't have to think about names specified in database (like when using alias in traditional SQL clauses).
Is this possible at all? Does it make sense?
To prevent writing a getter/setter methods for every single attribute of the model, you can override the magic methods from the Eloquent class to access them in camelCase style:
class Model extends Eloquent {
public function __get($key)
{
$snake_key = snake_case($key);
return parent::__get($snake_key);
}
public function __set($key, $value)
{
$snake_key = snake_case($key);
parent::__set($snake_key, $value);
}
public function __isset($key)
{
$snake_key = snake_case($key);
return parent::__isset($snake_key);
}
public function __unset($key)
{
$snake_key = snake_case($key);
parent::__unset($snake_key);
}
}
Would a getter method for your attribute help you? If yes:
<?php
class Task extends Eloquent {
public function isDone()
{
return $this->getAttribute('is_done');
}
}
If not, and you really need to access $Task->isDone: try to overwrite the $key in magic _get() method for $key == 'isDone' (and maybe other attributes) and return the parent::_get() with $key:
<?php
class Task extends Eloquent {
public function __get($key)
{
if($key == 'isDone')
$key = 'is_done';
return parent::__get($key);
}
}
And perhaps, your Eloquent needs an attribute mapper for the attribute magic methods ;)