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

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

Related

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

Laravel trait crud controller with request validation and resources

I'm trying to refactor my code to be more reusable.
I created a trait CrudControllerTrait to implement the index,show,store,update,destroy methods.
But I found 2 problems:
BrandController.php
public function store(BrandNewRequest $request)
{
$requestData = $request->validated();
return new BrandResource($this->brands->store($requestData));
}
ProductController.php
public function store(ProductNewRequest $request)
{
$requestData = $request->validated();
return new ProductResource($this->products->store($requestData));
}
The trait method would be:
public function store(xxxxx $request)
{
$requestData = $request->validated();
return new xxxxxResource($this->repository()->store($requestData));
}
Problem1: The hint type. How can I abstract them? If I remove it shows that errror:
"message": "Too few arguments to function App\\Http\\Controllers\\BrandController::store(), 0 passed and exactly 1 expected"
Problem2: Return the resource. How can create the new resource? On the collection I can solve it doing this:
public function index()
{
$models = $this->repository()->index();
return $this->resource()::collection($models);
}
The resource is on the controller who uses the trait:
public function resource()
{
return BrandResource::class;
}
But with single resource didn't know how to do it...
The idea is, that I have so much controllers using the same pattern: BrandController, ProductController, etc. I'd love to reuse these 5 crud methods on the same trait...
The only way I found is creating an abstract method.
trait CrudRepositoryTrait
{
abstract function model();
public function index()
{
return $this->model()::with($this->with())->get();
}
public function find($id)
{
return $this->model()::findOrFail($id);
}
public function store($data)
{
$request = $this->dtoRequest($data);
return $this->model()::create($request);
}
(...)
}
And then, an example how to use this treat:
class ProductRepository implements ProductRepositoryContract
{
use CrudRepositoryTrait;
function model()
{
return Product::class;
}
(...)
}
By this way I could reuse a lot of code.

Laravel - Eloquent override get method when using where

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.

Eloquent where doesn't work after overwriting model constructor

I am trying to develop a web application using laravel 5.3 and came up with a problem couldn't solve so far.
Heres the Context.
I got a simple Laravel Model called Section which implements a constructor as shown below;
public function __construct($title = null, array $attributes = array()){
parent::__construct($attributes);
try {
\App\logic_model\system\internal\Logger::debug_dump("~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
\App\logic_model\system\internal\Logger::debug_dump("create section ".$title);
$this->title = $title;
$this->save();
return $this;
} catch(\Exception $e){
\App\logic_model\system\internal\Logger::dev_dump($e->getMessage());
throw $e;
}
}
Instance creation using the constructor seems to be working pretty well.
I wrote a function find_by_title as shown below:
public static function find_by_title($title){
$section = \App\logic_model\sections\Section::where("title", "=", $title)->first();
return $section;
}
Here occurs the Problem (the unexpected behaviour): The Eloquent where function seems to call my overloaded constructor instead of the default constructor.
My question is: Why is that? How to fix it?
This is completely expected behavior. As you created custom constructor, each time new model is created (in fact, this happens when you call first(), not where) then this constructor is used to create new object.
If you need custom constructor like this, I would recommend you to create static custom method that will do the same for example like this:
public static function createWithTitle($title = null, array $attributes = array()){
$model = new static($attributes);
try {
\App\logic_model\system\internal\Logger::debug_dump("~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
\App\logic_model\system\internal\Logger::debug_dump("create section ".$title);
$model->title = $title;
$model->save();
return $model;
} catch(\Exception $e){
\App\logic_model\system\internal\Logger::dev_dump($e->getMessage());
throw $e;
}
}

Custom Eloquent relation which sometimes returns $this Model

I have a Model Text which has a 1-to-many-relation called pretext(), returning a 1-to-many-Relationshop to Text, like so:
class Text extends Model
{
public function pretext(){
return $this->belongsTo('App\Models\Text', 'pretext_id');
}
public function derivates(){
return $this->hasMany('App\Models\Text', 'pretext_id');
}
}
If a $text does not have any pretext (which, in my scenario, means $text['pretext_id'] == 0) $text->pretext() shall return the $text itself. When I try
public function pretext(){
if ( $this->belongsTo('App\Models\Text', 'pretext_id') ) {
return $this->belongsTo('App\Models\Text', 'pretext_id');
}
else {
return $this;
}
}
I get the error
local.ERROR: LogicException: Relationship method must return an object of type Illuminate\Database\Eloquent\Relations\Relation
when the else-part is executed. So my questions are:
How do I turn $this into an object of type Relation? or alternatively:
How can I achieve my goal on a different way?
Try dynamic props
class Text extends Model
{
protected $appends = ['content'];
public function pretext(){
return $this->belongsTo('App\Models\Text', 'pretext_id');
}
public function getContentAttribute(){
$pretext = $this->pretext()->get();
if ($pretext->count()) {
return $pretext;
}
return $this;
}
}
Then in controller or view if you have the instance
(consider optimizing it if you have N+1 issues)
$obj = Text::find(1);
dd($obj->content);
I think you can create another method that calling pretext() and check the returned value.
public function getPretext() {
$value = pretext();
return ($value)? $value : $this;
}

Resources