Override Eloquent Relation Create Method - laravel-5

I want to override create method, but with relation, it didn't touch the create method.
There are Two Models:
class User extends Model
{
public function user_detail()
{
return $this->hasOne(UserDetail::class);
}
}
class UserDetail extends Model
{
public static function create(array $attributes = [])
{
//I was trying to do something like
/*
if(isset($attributes['last_name']) && isset($attributes['first_name']))
{
$attributes['full_name']=$attributes['first_name'].' '.$attributes['last_name'];
}
unset($attributes['first_name'],$attributes['last_name']);
*/
Log::debug('create:',$attributes);
$model = static::query()->create($attributes);
return $model;
}
}
When I use UserDetail::create($validated), and there is a log in laravel.log, so I know the code touched my custom create method.
But if I use
$user = User::create($validated);
$user->user_detail()->create($validated);
There is no log in laravel.log, which means laravel didn't touch the create method, then how I supposed to do to override create method under this circumstance?(I'm using laravel 5.7)

Thank you #Jonas Staudenmeir, after I read the documentation, here is my solution.
If the $attributes are not in protected $fillable array, then I do it in the __construct method.
class UserDetail extends Model
{
protected $fillable=['full_name','updated_ip','created_ip'];
public function __construct(array $attributes = [])
{
if (isset($attributes['first_name']) && isset($attributes['last_name'])) {
$attributes['full_name'] = $attributes['first_name'].' '.$attributes['last_name'];
}
parent::__construct($attributes);
}
}
Otherwise, I do it in Observer.
namespace App\Observers;
use App\Models\UserDetail;
class UserDetailObserver
{
public function creating(UserDetail $userDetail)
{
$userDetail->created_ip = request()->ip();
}
public function updating(UserDetail $userDetail)
{
$userDetail->updated_ip = request()->ip();
}
}
Register Observer in AppServiceProvider.
namespace App\Providers;
use App\Models\UserDetail;
use App\Observers\UserDetailObserver;
class AppServiceProvider extends ServiceProvider
{
public function boot()
{
UserDetail::observe(UserDetailObserver::class);
}
}
I choose Observer instead of Event&Listener is for easy maintenance.

Related

How can I avoid repeating this line in all my methods?

I am working on a blogging application in Laravel 8.
I have a settings table from which I pull the directory name of the current theme.
class ArticlesController extends Controller {
public $theme_directory;
public function index() {
// Theme _directory
$this->theme_directory = Settings::all()[0]->theme_directory;
// All articles
$articles = Article::all();
return view('themes/' . $this->theme_directory . '/templates/index', ['articles' => $articles]);
}
public function show($slug) {
// Theme _directory
$this->theme_directory = Settings::all()[0]->theme_directory;
// Single article
$article = Article::where('slug', $slug)->first();
return view('themes/' . $this->theme_directory . '/templates/single', ['article' => $article]);
}
}
The problem
A you can see, the line $this->theme_directory = Settings::all()[0]->theme_directory is repeted in both methods (and would be repeted in others in the same way).
Question
How can I avoid this repetition (and make my code DRY)?
Inheritance approach
Inheritance for a controller would avoid you from repeating it.
abstract class CmsController extends Controller{
protected $themeDirectory;
public function __construct()
{
$this->themeDirectory= Settings::first()->theme_directory ?? null;
}
}
Extend it and you can access it like you have always done.
class ArticlesController extends CmsController
{
public function index() {
dd($this->themeDirectory);
}
}
Trait
Use traits which is partial classes, done by just fetching it, as it is used in different controllers the performance is similar to saving it to an property as it is never reused.
trait Themeable
{
public function getThemeDirectory()
{
return Settings::first()->theme_directory ?? null;
}
}
class ArticlesController extends CmsController
{
use Themeable;
public function index() {
dd($this->getThemeDirectory());
}
}
Static function on model
If your models does not contain to much logic, a static function on models could also be a solution.
class Setting extends model
{
public static function themeDirectory()
{
return static::first()->theme_directory ?? null;
}
}
class ArticlesController extends CmsController
{
use Themeable;
public function index() {
dd(Setting::themeDirectory());
}
}

Testing Custom Route Model Binding

I'm trying to test a custom class that implements \Illuminate\Contracts\Routing\UrlRoutable and can't get the resolveRouteBinding method invoked.
<?php
namespace Tests\Unit;
use Illuminate\Support\Facades\Route;
use Tests\TestCase;
class BindingExampleClassTest extends TestCase
{
function test_invoke_resolve_route_binding_method()
{
Route::get('/invoke-route-binding/{binding}', function (BindingExampleClass $binding) {
dd($binding);
});
$this->get('/invoke-route-binding/1');
}
}
class BindingExampleClass implements \Illuminate\Contracts\Routing\UrlRoutable
{
public $id;
public function resolveRouteBinding($value, $field = null)
{
$this->id = $value;
}
public function getRouteKey()
{
// TODO: Implement getRouteKey() method.
}
public function getRouteKeyName()
{
// TODO: Implement getRouteKeyName() method.
}
public function resolveChildRouteBinding($childType, $value, $field)
{
// TODO: Implement resolveChildRouteBinding() method.
}
}
The dd response is BindingExampleClass with id still null.
Registering route inside a test function will not include any middleware. When working with route model binding in Laravel, \Illuminate\Routing\Middleware\SubstituteBindings::class middleware must be defined in the router instance.
Route::get('/invoke-route-binding/{binding}', function (BindingExampleClass $binding) {
dd($binding);
})->middleware(\Illuminate\Routing\Middleware\SubstituteBindings::class);

Call to undefined relationship on model

We have the following class using $with:
class CargaHorasEmpleado extends Model
{
protected $table = "empleados_horas";
protected $with = ["tipoTarea", "proyecto", "empleado", "empleadoQueHizoLaCarga"];
public function tipoTarea()
{
return $this->belongsTo('App\TipoTarea', 'id_tipo_tarea', 'id')->withTrashed();
}
public function empleado()
{
return $this->belongsTo('App\Empleado', 'id_empleado', 'id')->withTrashed();
}
public function empleadoQueHizoLaCarga()
{
return $this->belongsTo('App\Empleado', 'id_empleado_cargo_hs', 'id')->withTrashed();
}
public function proyecto()
{
return $this->belongsTo('App\Proyecto', 'id_proyecto', 'id')->withTrashed();
}
}
This is the class TipoTarea
namespace App;
use Illuminate\Database\Eloquent\Model;
class TipoTarea extends Model
{
protected $table = 'tipos_tareas';
public $timestamps = false;
protected $fillable = [
'titulo', 'descripcion'
];
}
Thep page throws the error: "Call to undefined relationship [tipoTarea] on model [App\CargaHorasEmpleado]". That's the only relationship that's not working. The others are fine. What's wrong?
Well, isn't the relationship called "tipoTarea"? You wrote "tiposTarea"
The problem was that my class "TipoTarea" didn't use softdeletes. So the error was in using the "WithTrashed" method. The correct way is:
public function tipoTarea()
{
return $this->belongsTo('App\TipoTarea', 'id_tipo_tarea', 'id');
}

Add function to laravel Notification

I have next Notification class:
class WelcomeNotification extends Notification
{
use Queueable;
public function __construct()
{
//
}
public function via($notifiable)
{
return ['database'];
}
public function toDatabase($notifiable)
{
return [
//
];
}
}
And I want add some function to this. For example:
public function myFunction()
{
return 'something';
}
But $user->notifications->first()->myFunction return nothing
When you call the notifications() relation it turns out is a polymorphic relation using the DatabaseNotification model. The proper way is to inherit DatabaseNotification and write the custom function their.
For example, create app/DatabaseNotification/WelcomeNotification.php and inherit DatabaseNotification model.
namespace App\DatabaseNotification;
use Illuminate\Notifications\DatabaseNotification;
class WelcomeNotification extends DatabaseNotification
{
public function foo() {
return 'bar';
}
}
And override the notifications() function that uses the Notifiable trait:
use App\DatabaseNotification\WelcomeNotification;
class User extends Authenticatable
{
use Notifiable;
...
public function notifications()
{
return $this->morphMany(WelcomeNotification::class, 'notifiable')
->orderBy('created_at', 'desc');
}
...
}
And now you can call the custom function as follows:
$user->notifications->first()->foo();

Laravel Multiple module using one Controller

Now I have few modules created like Product, Sale, Category. I found out they actually using same function with similar process. For example update() in Controller :
Category
public function update($id)
{
$instance = Category::findOrFail($id);
$instance->fill(Input::all())->save();
}
Product
public function update($id)
{
$instance = Product::findOrFail($id);
$instance->fill(Input::all())->save();
}
How can I join it together to BaseController by just make the Model dynamic?
Something like this:
abstract class ResourceController extends BaseController
{
protected $entity;
public function __construct(Model $entity){ //or Eloquent, depending on your import alias
$this->entity = $entity;
}
public function update($id)
{
$instance = $this->entity->findOrFail( $id );
$instance->fill( Input::all() )->save();
}
}
class ProductController extends ResourceController{
public function __construct(Product $entity){
parent::__construct($entity);
}
}
class CategoryController extends ResourceController{
public function __construct(Category $entity){
parent::__construct($entity);
}
}

Resources