Laravel - Eloquent override get method when using where - laravel

I override eloquent get() method in one of my models OrderStatus
public static function get()
{
return "hit";
}
when I call it without where it's working fine
>>> $order_statuses = OrderStatus::get();
=> "hit"
But when I call it with where it uses the parent get method again:
>>> $order_statuses = OrderStatus::where('order_id', 24)->get();
=> Wilgucki\Csv\CsvCollection {#4434
all: [],
}
Is there a way to override it anyway?

you can do that by overriding the get() method inside the query builder in \Illuminate\Database\Query\Builder, an example of this override is provided in this medium post. But in your case it seems you want to override it only when used against the OrderStatuses model.
The good news is that the Builder class has a reference to the table:
/**
* The table which the query is targeting.
*
* #var string
*/
public $from;
it is set here:
/**
* Set the table which the query is targeting.
*
* #param string $table
* #return $this
*/
public function from($table)
{
$this->from = $table;
return $this;
}
so you can do something like this:
namespace App\Override;
class QueryBuilder extends \Illuminate\Database\Query\Builder {
//#Override
public function get($columns = ['*']) {
if ($this->from == 'OrderStatus') {
// customize the call
} else {
//Return default
return parent::get($columns);
}
}
}

The get() function is not defined on the Model class but it is called as a dynamic method on the Eloquent QueryBuilder, that is is handled by calling on the Model class this function:
public static function __callStatic($method, $parameters)
At the end when you call the get() function on Model you are instead calling it dynamically on the Illuminate\Database\Query\Builder class.
So you can't really override the chainable method get() on a class derived from Model without messing up things.

Related

Laravel relationship with additional where statement

I know I can define a relationship by
Class Users extends Model{
function profile(){
return $this->hasOne(Profile::Class);
}
}
is there a way like adding extra query to the relationship like other than foreign key and local key that is available to define, I want to only get those records of Profile model that field active contains a value of 1. Profile model has a field named active. Any help, ideas is greatly appreciated, thank you in advance.
you can simply try
return $this->hasOne(Profile::Class)->where('active', 1);
but better approach will be using Scope like this.
create a folder app/Scopes and add a new file ActiveUserOnly.php
place this code there
namespace App\Scopes;
use \Illuminate\Database\Eloquent\Builder;
use \Illuminate\Database\Eloquent\Scope;
use \Illuminate\Database\Eloquent\Model;
class ActiveUsersOnly implements Scope {
/**
* #inheritdoc
*
* #param Builder $builder
* #param Model $model
*
* #return Builder|void
*/
public function apply( Builder $builder, Model $model ) {
return $builder->where( 'active', '=', true );
}
}
add this code to the top of Profile model.
use App\Scopes\ActiveProfilesOnly;
add this code in your Profile model.
protected static function boot() {
parent::boot();
static::addGlobalScope( new ActiveProfilesOnly() );
}
then this code will work in your User model.
Class Users extends Model{
function profile(){
return $this->hasOne(Profile::Class);
}
}

Laravel 8 vendor class `Illuminate\Database\Eloquent\Factories\Factory` can't resolve name of ModelNameFactory class

Laravel 8 has the default App/Models directory for Model classes. The Illuminate\Database\Eloquent\Factories\Factory has static function resolveFactoryName() to resolve name of ModelNameFactory class
public static function resolveFactoryName(string $modelName)
{
$resolver = static::$factoryNameResolver ?: function (string $modelName) {
$modelName = Str::startsWith($modelName, 'App\\Models\\')
? Str::after($modelName, 'App\\Models\\')
: Str::after($modelName, 'App\\');
return static::$namespace.$modelName.'Factory';
};
return $resolver($modelName);
}
The function works properly only for App/ModelName or App/Models/ModelName
if name of Model class, for example, is the Domain/Customers/Models/ModelName, that function doesn't work properly. What is the best way to fix it?
As you can see here, there is a method called guessFactoryNamesUsing which lets you tell Laravel how it should guess the name of your factories.
Add the following to your AppServiceProvider:
use Illuminate\Database\Eloquent\Factories\Factory;
public function register()
{
Factory::guessFactoryNamesUsing(function ($class) {
return 'Database\\Factories\\' . class_basename($class) . 'Factory';
});
}
Source:
/**
* Specify the callback that should be invoked
* to guess factory names based on dynamic relationship names.
*
* #param callable $callback
* #return void
*/
public static function guessFactoryNamesUsing(callable $callback)
{
static::$factoryNameResolver = $callback;
}
Please put this in your model class in App\Models\ModelName.
Make sure the ModelFactory is the factory name.
protected static function newFactory()
{
return \Modules\Module\Database\Factories\ModelFactory::new();
}

lararvel uuid as primary key

I'm trying to set an uuid as primary key in a Laravel Model. I've done it setting a boot method in my model as stablished here so I don't have to manually create it everytime I want to create and save the model. I have a controller that just creates the model and saves it in database.
It is saved correctly in database but when controller returns the value of the id is always returned with 0. How can I make it to actually return the value that it is creating in database?
Model
class UserPersona extends Model
{
protected $guarded = [];
protected $casts = [
'id' => 'string'
];
/**
* Setup model event hooks
*/
public static function boot()
{
parent::boot();
self::creating(function ($model) {
$uuid = Uuid::uuid4();
$model->id = $uuid->toString();
});
}
}
Controller
class UserPersonaController extends Controller
{
public function new(Request $request)
{
return UserPersona::create();
}
}
You need to change the keyType to string and incrementing to false. Since it's not incrementing.
public $incrementing = false;
protected $keyType = 'string';
Additionally I have an trait which I simply add to those models which have UUID keys. Which is pretty flexible. This comes originally from https://garrettstjohn.com/articles/using-uuid-laravel-eloquent-orm/ and I added some small adjustments to it for issues which I have discovered while using it intensively.
use Illuminate\Database\Eloquent\Model;
use Ramsey\Uuid\Uuid;
/**
* Class Uuid.
* Manages the usage of creating UUID values for primary keys. Drop into your models as
* per normal to use this functionality. Works right out of the box.
* Taken from: http://garrettstjohn.com/entry/using-uuids-laravel-eloquent-orm/
*/
trait UuidForKey
{
/**
* The "booting" method of the model.
*/
public static function bootUuidForKey()
{
static::retrieved(function (Model $model) {
$model->incrementing = false; // this is used after instance is loaded from DB
});
static::creating(function (Model $model) {
$model->incrementing = false; // this is used for new instances
if (empty($model->{$model->getKeyName()})) { // if it's not empty, then we want to use a specific id
$model->{$model->getKeyName()} = (string)Uuid::uuid4();
}
});
}
public function initializeUuidForKey()
{
$this->keyType = 'string';
}
}
Hope this helps.
Accepted answer not worked for me on Laravel 9, but this way worked perfect, you can try it:
1- Create new Trait Class in project path app/Traits/IdAsUuidTrait.php (if you not found Traits folder create it, this is full code of this Class:
<?php
namespace App\Traits;
use Illuminate\Support\Str;
trait IdAsUuidTrait
{
public function initializeIdAsUuidTrait(): void
{
$this->keyType = 'string';
$this->id = Str::orderedUuid()->toString();
}
}
2- In any model you want to make id as UUID just call trait like this:
use App\Traits\IdAsUuidTrait;
class YourModelName extends Model
{
use IdAsUuidTrait;
...
That is it, now try to create, select, update any row in database by this model...

Relationships in InfyOm Generator

I have News and NewsCategories models which I have generated CRUD for using the relationship option.
I now need to generate a select list for the News model to select the NewsCategory it belongs to.
I know how to do this in the model but no idea how to do it using the repository pattern.
I can't see any examples in the docs so any help with this would be appreciated.
Thanks
NewsRepository
/**
* Configure the Model
**/
public function model()
{
return News::class;
}
News Model
/**
* #return \Illuminate\Database\Eloquent\Relations\BelongsTo
**/
public function newsCategory()
{
return $this->belongsTo(NewsCategory::class);
}
News Controller
/**
* Show the form for creating a new News.
*
* #return Response
*/
public function create()
{
return view('news.create');
}
/**
* Store a newly created News in storage.
*
* #param CreateNewsRequest $request
*
* #return Response
*/
public function store(CreateNewsRequest $request)
{
$input = $request->all();
$news = $this->newsRepository->create($input);
Flash::success('News saved successfully.');
return redirect(route('news.index'));
}
If your repository extends InfyOm\Generator\Common\BaseRepository. The repository should update the model relations by it self. Just pass the relation values alongside the other inputs with the correct keys.
However, for deleting and reading (let's call them actions), you will need to query your data.
You can do that using repository methods, scope queries, or criteria classes.
(and call those filters).
Repository Methods:
// inside your controller
// some repository filtering method
$this->repository->whereHas('newsGroup', function($query){...});
$this->repository->hidden(['field_to_hide']);
...
// some action: delete, all or findWhere...
$this->repository->delete();
Scope Queries are callbacks that apply some queries on the model eloquent and return it.(unlike Eloquent scopes which accept and return Database\Eloquent\Builder)
$this->repository->scopeQuery(
function ($model){ return $model->where(...);
});
Or your
// some action: delete, update or findWhere...
$this->repository->delete();
The Criteria Way: you will create a class responsible on querying. It is an overkill for the simple use-cases.
// inside the controller
$this->repository->pushCriteria(new NewsBelongingToCategory ($group_id));
// App\Criteria\NewsBelongingToCategory.php
class NewsBelongingToCategory implements CriteriaInterface {
private $group_id;
public function __construct($group_id){
$this->group_id = $group_id;
}
public function apply($model, NewsRepositoryInterface $repository)
{
$group_id = $this->group_id;
$model = $model->whereHas('newsCategory',
function ($query) use ($group_id){
$query->where('group_id', '=', $group_id);
});
return $model;
}
}
// in your controller
$this->repository->delete();
Note that some actions ignore specific filters. For example, delete(id) and update($attributes, $id) does not use criteria, in the other hand lists($column, $key) does not use scopes.

Laravel static call of Overrided Eloquent's first() method

if I override eloquent's first() method I can not call the method statically (Through facade) as I would expect. I would expect that implemented __callStatic() method will be used (implemented in Illuminate\Database\Eloquent\Model), but that's not the case.
So I tried to implement the magic method myself. I still cannot access overrided first() method statically.
ErrorException: Non-static method Entity::first() should not be
called statically, assuming $this from incompatible context.
class Entity extends Eloquent
{
public function first($columns = ['*'])
{
if (Cache::tags(static::getTags())->has('first')) :
return Cache::tags(static::getTags())->get('first');
endif;
$result = parent::first($columns);
if ($result) :
Cache::tags(static::getTags())->put('first', $result, with(new static)->ttl);
endif;
return $result;
}
public static function __callStatic($method, $parameters)
{
$instance = new static;
return call_user_func_array(array($instance, $method), $parameters);
}
}
What I'm missing here?
I've expressed my doubts about the necessity to override first() for caching purposes, but of course it is technically possible to do it.
You have to create your own Builder class for that:
class MyBuilder extends Illuminate\Database\Eloquent\Builder {
/**
* Execute the query and get the first result.
*
* #param array $columns
* #return \Illuminate\Database\Eloquent\Model|static|null
*/
public function first($columns = array('*'))
{
if ($someCondition) :
return 1;
endif;
return parent::first($columns);
}
}
And then make your model use this builder by implementing the newEloquentBuilder method:
public function newEloquentBuilder($query)
{
return new MyBuilder($query);
}

Resources