Laravel extends class with constructor using DI - laravel

I have created a small CRUD controller, I need to inject FormBuilder in the __construct() of this CRUD controller
class CrudController extends Controller
{
private $formBuilder;
/**
* CrudController constructor.
* #param FormBuilder $formBuilder
*/
public function __construct(FormBuilder $formBuilder)
{
$this->middleware('auth');
$this->formBuilder = $formBuilder;
// ....
My question is : can I extends this class without injecting FormBuilder to each child ? For now I have :
class LevelsCrudController extends CrudController
{
public function __construct(FormBuilder $formBuilder)
{
parent::__construct($formBuilder);
// .....
Which is really redondant, any workaround ?

If you do not want to have a FormBuilder $formBuilder in your child controller's construct, you could pass it as a param only when you call the parent's construct like this:
parent::__construct(resolve(FormBuilder::class));
Otherwise, you could remove it completely from the parent's constructor and resolve it inside the parent constructor like this:
class CrudController extends Controller
{
public function __construct()
{
$this->middleware('auth');
$this->formBuilder = resolve(FormBuilder::class);
// ...
}
}
And then in your child, you can simple call:
public function __construct()
{
parent::__construct();
// ...
}

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

Laravel Controller: Override parent request validation

I have a structure that has base controller called BaseController that all controllers extend it, and it has implementation of index() to view resources, base controller use default Request validation and I need to override this in a child.
When I run the below code I receive the following error:
Declaration of ...PostController::index(...PostRequest $request) should be compatible with ...BaseController::index(...Request $request)
Parent:
/* BaseController.php */
namespace App\Http\Controllers;
use Illuminate\Http\Request;
abstract class BaseController extends Controller
{
public function index(Request $request)
{
// logic
}
}
Child:
/* PostController.php */
namespace App\Http\Controllers\App;
use Illuminate\Http\PostRequest;
class PostController extends BaseController
{
public function index(PostRequest $request)
{
// logic
}
}

Override Eloquent Relation Create Method

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.

How to inject repository pattern to event Laravel

I use repository patter. If I want use repistory in my controller(for example UserRepository) I simple inject repository in my controller
class SomeController extend BaseController
public function __contruct(UserRepositoryInterface $user){
$this->user = $user;
}
In this example laravel automaticlly inject repository to my controller. But now I would like make some operation on my database from event. So I would like inject UserRepository to my event. How can I do it?
class UserHandlerEvent {
public function onCreate($event){}
public function subscribe($events){
$event->listen('user.create', 'UserHandlerEvent#onCreate');
}
This is my event, next I regiter it in my EventServiceProvider. It is looks like
class EventServiceProvider extends ServiceProvider
{
public function register(){
$this->app->events->subscribe(new UserEventHandler());
}
}
What should I do, if I want have to access to UserRepository from my event?
Just create a constructor in your UserHandlerEvent class and inject the UserRepositoryInterface as you did in your controller.
class UserHandlerEvent {
protected $user;
public function __contruct(UserRepositoryInterface $user){
$this->user = $user;
}
public function onCreate($event){}
public function subscribe($events){
$event->listen('user.create', 'UserHandlerEvent#onCreate');
}

Type hinting parent::__construct() arguments in controllers

I've got a BaseController in a Laravel Framework based App with the following code:
class BaseController extends Controller {
public function __construct(Credentials $credentials) {
$this->credentials = $credentials;
}
Then, all my other controllers will Extend the BaseController:
class PostController extends BaseController {
public function __construct(PostRepository $post)
{
$this->post = $post;
parent::__construct();
}
However, I'd need to type hint the Credentials Class in the parent::__construct(); of all my controllers. Is there any way to avoid that?
Thanks in advance
I can solve it using the following code:
class BaseController extends Controller {
public function __construct()
{
$this->credentials = App::make('Credentials'); // make sure to use the fully qualified namespace of Credentials if need be
}
}

Resources