12

I am modifying the CMS that I use in my projects, and recently I decided to create a controller for default actions BaseController, where all other controllers will extend this controller BaseController.

<?php

namespace App\Http\Controllers\Admin;

use App\Http\Controllers\Controller;

class BaseController extends Controller
{
    protected $viewFolder = 'admin';

    protected $title;

    protected $model;

    protected $key = 'id';

    protected $files = [];

    public function __construct()
    {
        $this->setVariable('title', $this->title);
    }

    public function index()
    {
        $items = $this->model::paginate();

        $this->setVariable('items', $items);

        return $this->viewRender('index');
    }

    public function create()
    {
        return $this->viewRender('create');
    }

    public function store(ExampleStoreRequestFROMEXAMPLECONTROLLER $request)
    {
        $item = $this->model::create($request->all());

        return redirect()->route($this->viewFolder.'.'.$this->viewType.'.show', $item[$this->key]);
    }

    public function show($id)
    {
        $item = $this->model::where($this->key, $id)->firstOrFail();

        $this->setVariable('item', $item);

        return $this->viewRender('show');
    }

    public function edit($id)
    {
        $item = $this->model::where($this->key, $id)->firstOrFail();

        $this->setVariable('item', $item);

        return $this->viewRender('edit');
    }

    public function update(ExampleUpdateRequestFROMEXAMPLECONTROLLER $request, $id)
    {
        $item = $this->model::where($this->key, $id)->firstOrFail();

        $item->update($request->except(['_token', '_method']));

        return redirect()->route($this->viewFolder.'.'.$this->viewType.'.show', $item[$this->key]);
    }

    public function status(ExampleStatusRequestFROMEXAMPLECONTROLLER $request, $id)
    {
        $this->model::where($this->key, $id)->update($request->except('_method'));

        return response()->json([
            'message' => 'O status foi alterado com sucesso'
        ]);
    }

    public function destroy($id)
    {
        $this->model::where($this->key, $id)->delete();

        return redirect()->route($this->viewFolder.'.'.$this->viewType.'.index');
    }
}

The problem is: I wrote the BaseController using the Form Requests of the UserController and I have no idea how to leave these Request dynamic so that I can implement them from other controllers.

<?php

namespace App\Http\Controllers\Admin;

use App\Http\Requests\ExampleStatusRequest;
use App\Http\Requests\ExampleStoreRequest;
use App\Http\Requests\ExampleUpdateRequest;

class ExampleController extends BaseController
{
    protected $viewType = 'users';

    protected $model = 'App\Example';

    public function index()
    {
        $this->setVariable('title', 'Usuários');
        return parent::index();
    }

    public function create()
    {
        $this->setVariable('title', 'Cadastrar usuário');
        return parent::create();
    }

    public function store(ExampleStoreRequest $request)
    {
        return parent::store($request);
    }

    public function edit($id)
    {
        $this->setVariable('title', 'Editar usuário');
        return parent::edit($id);
    }

    public function update(ExampleUpdateRequest $request, $id)
    {
        return parent::update($request, $id);
    }

    public function status(ExampleStatusRequest $request, $id)
    {
        return parent::status($request, $id);
    }
}

Here is my default controller:

<?php

namespace App\Http\Controllers;

use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Routing\Controller as BaseController;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;

class Controller extends BaseController
{
    use AuthorizesRequests, DispatchesJobs, ValidatesRequests;

    protected $viewFolder = '';

    protected $viewType = '';

    protected $viewVariables = [];

    protected function setVariable($key, $value)
    {
        $this->viewVariables[$key] = $value;
    }

    protected function viewRender($view)
    {
        return view($this->viewFolder.'.pages.'.$this->viewType.'.'.$view, $this->viewVariables);
    }
}

Any idea how to solve this?

Caio Kawasaki
  • 2,894
  • 6
  • 33
  • 69
  • Could use php's [func_get_args](http://www.php.net/manual/en/function.func-get-args.php). – Brian Lee May 05 '18 at 10:55
  • If I understand correctly what you are trying to do then the simple answer is: There's no clean way to do this in PHP. PHP really sucks like this. There's little freedom for (slightly more)advanced object oriented or generic programming. What this all comes down to is that the reason that you are asking this question is that you are advancing in polymorphic thinking in programming. PHP Lacks your level of thinking which means it's time to move on! Start using more object oriented languages (maybe look into C# (and ASP.net)). – Joas May 07 '18 at 21:21
  • I like the `viewRender` (or renderView, might I suggest) implementation. However, what happened to keeping it simple? Won't this implementation leak out a lot of low level details? Is it really that worth it? –  May 08 '18 at 12:06
  • Why are you looking for shortcuts? Begginers tend to do it this way (been there, done that). In this case small duplication is better than an abstraction. Your abstract class will surely grow and increase in complexity and in the as more and more use-cases are introduced. You will end with a big ball of mud which will be a hell to maintain just because you wanted to write less code in the first place. Let controllers be as simple as possible, standalone, And favor composition over inheritance, always. I am used to DDD now and I never use `extends` anymore. – Mike Doe May 09 '18 at 09:05

4 Answers4

8

After the comments I could understand better. I had the same issue here and that's my workaround

class BaseController extends Controller
{

     protected function _store($request)
     {
     ....
     }
 ...
 }

 class MyController extends BaseController
 {
 ....
      public function store(MyRequest $request)
      {
          //do something
          return parent::_store($request);
      }
 }
Felippe Duarte
  • 14,901
  • 2
  • 25
  • 29
3

Short answer:

Bindings.

One Route::model.

And one Simple Binding for each Form Request type, using Interfaces for the Method Injection in the Controller.

Long answer applied to your example:

Models binding in routes/web.php:

<?php

Route::model('example', App\Example::class);
Route::resource('example', 'Admin\ExampleController');

Requests bindings in the Controller Base:

<?php

namespace App\Http\Controllers\Admin;

use App\Http\Requests;
use App\Http\Controllers\Controller;

class BaseController extends Controller
{
    // Your code...

    /**
     * @var string[]|callable[]
     */
    protected $bindings = [];

    /**
     * Controller constructor.
     */
    public function __construct()
    {
        $this->addBindings();
    }

    /**
     * Add controller specific bindings.
     */
    protected function addBindings()
    {
        $app = Container::getInstance();

        foreach ($this->getBindings() as $abstract => $concrete) {
            $app->bind($abstract, $concrete);
        }
    }

    // Your code...

    public function store(Requests\StoreRequestInterface $request)
    {
        // Your code...
    }

    public function show(Model $item)
    {
        // Your code...
    }

    public function edit(Model $item)
    {
        // Your code...
    }

    public function update(Requests\UpdateRequestInterface $request, Model $model)
    {
        // Your code...
    }

    public function status(Requests\StatusRequestInterface $request, Model $model)
    {
        // Your code...
    }

    public function destroy(Model $item)
    {
        // Your code...
    }

    // Your code...
}

And the Example Controller:

<?php

namespace App\Http\Controllers\Admin;

use App\Http\Requests;

class ExampleController extends BaseController
{
    // Your code...

    /**
     * @var string[]|callable[]
     */
    protected $bindings = [
        Requests\StatusRequestInterface::class => Requests\ExampleStatusRequest::class,
        Requests\StoreRequestInterface::class  => Requests\ExampleStoreRequest::class,
        Requests\UpdateRequestInterface::class => Requests\ExampleUpdateRequest::class,
    ];

    // Your code...
}

So the Requests interfaces would look something like:

<?php

namespace App\Http\Requests;

interface StoreRequestInterface
{
}

And you should use it as an interface in your Form Requests:

<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class ExampleStoreRequest extends FormRequest implements StoreRequestInterface
{
    /**
     * Determine if the user is authorized to make this request.
     *
     * @return bool
     */
    public function authorize()
    {
        return false;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        return [
            //
        ];
    }
}

Opinionated alternative answer:

General controller base:

<?php

namespace App\Http\Controllers;

use Illuminate\Container\Container;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Routing\Controller as BaseController;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;

abstract class Controller extends BaseController
{
    use AuthorizesRequests, DispatchesJobs, ValidatesRequests;

    /**
     * The view subdirectory that must be used.
     *
     * @var string
     */
    protected $viewDir;

    /**
     * Bindings
     *
     * @var string[]|callable[]
     */
    protected $bindings = [];

    /**
     * Controller constructor.
     */
    public function __construct()
    {
        $this->addViewPath();
        $this->addBindings();
        $this->init();
    }

    /**
     * @return void
     */
    protected function init()
    {
        //
    }

    /**
     * @return string
     */
    protected function getViewDir()
    {
        return $this->viewDir;
    }

    /**
     * @return callable[]|string[]
     */
    protected function getBindings()
    {
        return $this->bindings;
    }

    /**
     * Add controller specific view path.
     */
    protected function addViewPath()
    {
        if (null !== ($dir = $this->getViewDir()) && ($path = realpath(base_path('resources/views/' . $dir)))) {
            view()->getFinder()->addLocation($path);
        }
    }

    /**
     * Add controller specific bindings.
     */
    protected function addBindings()
    {
        $app = Container::getInstance();

        foreach ($this->getBindings() as $abstract => $concrete) {
            $app->bind($abstract, $concrete);
        }
    }
}

Resource Controller Base:

<?php

namespace App\Http\Controllers;

use Illuminate\Database\Eloquent\Model;
use App\Http\Requests\StoreRequestInterface;
use App\Http\Requests\UpdateRequestInterface;
use App\Http\Requests\StatusRequestInterface;

abstract class ResourceController extends Controller
{
    /**
     * Model class
     *
     * @var string
     */
    protected $modelClass;

    /**
     * @var string
     */
    protected $viewDirPrefix;

    /**
     * @return string
     */
    protected function getViewDir()
    {
        return $this->viewDir ?:
            ltrim($this->viewDirPrefix . DIRECTORY_SEPARATOR . 'pages', DIRECTORY_SEPARATOR) .
            DIRECTORY_SEPARATOR . strtolower(class_basename($this->modelClass));
    }

    /**
     * @return \Illuminate\Database\Eloquent\Builder
     */
    protected function getQuery()
    {
        return call_user_func($this->modelClass . '::query');
    }

    /**
     * @return string
     */
    protected function getName()
    {
        return __(class_basename($this->modelClass));
    }

    /**
     * Display a listing of the resource.
     *
     * @return \Illuminate\Http\Response
     */
    protected function index()
    {
        $title = __('resource.title.index', ['name' => str_plural($this->getName())]);
        $models = $this->getQuery()->paginate();

        return view('index', compact('title', 'models'));
    }

    /**
     * Show the form for creating a new resource.
     *
     * @return \Illuminate\Http\Response
     */
    protected function create()
    {
        $title = __('resource.title.create', ['name' => $this->getName()]);

        return view('create', compact('title'));
    }

    /**
     * Store a newly created resource in storage.
     *
     * @param  \Illuminate\Http\Request|StoreRequestInterface $request
     *
     * @return \Illuminate\Http\Response
     */
    public function store(StoreRequestInterface $request)
    {
        $model = $this->getQuery()->create($request->all());

        return redirect()
            ->route(substr($request->route()->getName(), 0, -5) . 'show', $model);
    }

    /**
     * Display the specified resource.
     *
     * @param  \Illuminate\Database\Eloquent\Model $model
     *
     * @return \Illuminate\Http\Response
     */
    public function show(Model $model)
    {
        $title = __('resource.title.show', ['name' => $this->getName()]);

        return view('show', compact('title', 'model'));
    }

    /**
     * Show the form for editing the specified resource.
     *
     * @param  \Illuminate\Database\Eloquent\Model $model
     *
     * @return \Illuminate\Http\Response
     */
    public function edit(Model $model)
    {
        $title = __('resource.title.edit', ['name' => $this->getName()]);

        return view('edit', compact('title', 'model'));
    }

    /**
     * Update the specified resource in storage.
     *
     * @param  \Illuminate\Http\Request|UpdateRequestInterface $request
     * @param  \Illuminate\Database\Eloquent\Model             $model
     *
     * @return \Illuminate\Http\Response
     */
    public function update(UpdateRequestInterface $request, Model $model)
    {
        $model->update($request->except(['_token', '_method']));

        return redirect()
            ->route(substr($request->route()->getName(), 0, -6) . 'show', $model);
    }

    /**
     * Remove the specified resource from storage.
     *
     * @param  \Illuminate\Database\Eloquent\Model $model
     *
     * @return \Illuminate\Http\Response
     * @throws \Exception
     */
    public function destroy(Model $model)
    {
        $model->delete();

        return redirect()
            ->route(substr(request()->route()->getName(), 0, -7) . 'index');
    }

    /**
     * @param \Illuminate\Http\Request|StatusRequestInterface $request
     * @param \Illuminate\Database\Eloquent\Model             $model
     *
     * @return \Illuminate\Http\Response|\Illuminate\Http\JsonResponse
     */
    protected function status(StatusRequestInterface $request, Model $model)
    {
        $model->update($request->except('_method'));

        return response()->json([
            'message' => __('resource.status.success'),
        ]);
    }
}

Resource controller for Example model:

<?php

namespace App\Http\Controllers\Admin;

use App\Example;
use App\Http\Requests;
use App\Http\Controllers\ResourceController;

class ExampleController extends ResourceController
{
    /**
     * Model class
     *
     * @var string
     */
    protected $modelClass = Example::class;

    /**
     * @var string
     */
    protected $viewDirPrefix = 'admin';

    /**
     * @var string[]|callable[]
     */
    protected $bindings = [
        Requests\StatusRequestInterface::class => Requests\Example\StatusRequest::class,
        Requests\StoreRequestInterface::class  => Requests\Example\StoreRequest::class,
        Requests\UpdateRequestInterface::class => Requests\Example\UpdateRequest::class,
    ];
}

English Language file in resources/lang/en/resource.php:

<?php

return [
    'title'  => [
        'index'  => 'All :Name',
        'create' => 'Create :Name',
        'show'   => 'Show :Name',
        'edit'   => 'Edit :Name',
    ],
    'status' => [
        'success' => 'Status updated successfully',
    ],
];

Portuguese Language file in resources/lang/pt/resource.php:

<?php

return [
    'title'  => [
        'index'  => 'Todos os :Name',
        'create' => 'Cadastrar :Name',
        'show'   => 'Show :Name',
        'edit'   => 'Editar :Name',
    ],
    'status' => [
        'success' => 'O status foi alterado com sucesso',
    ],
];

Web Routes in routes/web.php:

<?php

Route::group([
    'as'        => 'admin.',
    'prefix'    => 'admin',
    'namespace' => 'Admin',
], function () {
    Route::model('example', App\Example::class);
    Route::resource('example', 'ExampleController');
});

Request interfaces (for binding):

<?php

namespace App\Http\Requests;

interface StoreRequestInterface
{
}

Same for App\Http\Requests\UpdateRequestInterface and App\Http\Requests\StatusRequestInterface...

Example Form Request:

<?php

namespace App\Http\Requests\Example;

use Illuminate\Foundation\Http\FormRequest;
use App\Http\Requests\StoreRequestInterface;

class StoreRequest extends FormRequest implements StoreRequestInterface
{
    /**
     * Determine if the user is authorized to make this request.
     *
     * @return bool
     */
    public function authorize()
    {
        return false;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        return [
            //
        ];
    }
}

Same for App\Http\Requests\Example\UpdateRequest and App\Http\Requests\Example\StatusRequest...

Documentation applied for this reply:

Notice: This reply is for Laravel 5.6. Some code used in this reply is not supported in previews versions. If you want it for a specific version, let me know and I will try to adapt it.

Gonzalo
  • 400
  • 1
  • 9
2

Any CustomFormRequest that you will use, will be an instance of FormRequest.. a class that in turn extends the Request class. So in your BaseController do this:

BaseController.php

use Illuminate\Http\Request;

// some code

    public function store(Request $request)
    {
        $item = $this->model::create($request->all());

        return redirect()
            ->route($this->viewFolder.'.'.$this->viewType.'.show', item[$this->key]);
        }

// The rest of your code..

Then in your child Controllers:

AChildController.php

class AChildController extends BaseController
{

use App\Http\Requests\ACustomFormRequest;

// Some code

  public function store(ACustomFormRequest $request)
  {
      //do something
      return parent::_store($request);
  }

}

Give it a try.

Kenny Horna
  • 13,485
  • 4
  • 44
  • 71
1
class BaseController extends Controller
{

     protected function _store($request)
     {
     ....
     }
 ...
 }

 class MyController extends BaseController
 {
 ....
      public function store(MyRequest $request)
      {
          //do something
          return parent::_store($request);
      }
 }