I have a BaseController that provides the foundation for most HTTP methods for my API server, e.g. the store method:
BaseController.php
/**
* Store a newly created resource in storage.
*
* #return Response
*/
public function store(Request $request)
{
$result = $this->repo->create($request);
return response()->json($result, 200);
}
I then extend on this BaseController in a more specific controller, such as the UserController, like so:
UserController.php
class UserController extends BaseController {
public function __construct(UserRepository $repo)
{
$this->repo = $repo;
}
}
This works great. However, I now want to extend UserController to inject Laravel 5's new FormRequest class, which takes care of things like validation and authentication for the User resource. I would like to do this like so, by overwriting the store method and using Laravel's type hint dependency injection for its Form Request class.
UserController.php
public function store(UserFormRequest $request)
{
return parent::store($request);
}
Where the UserFormRequest extends from Request, which itself extends from FormRequest:
UserFormRequest.php
class UserFormRequest extends Request {
/**
* Determine if the user is authorized to make this request.
*
* #return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
return [
'name' => 'required',
'email' => 'required'
];
}
}
The problem is that the BaseController requires a Illuminate\Http\Request object whereas I pass a UserFormRequest object. Therefore I get this error:
in UserController.php line 6
at HandleExceptions->handleError('2048', 'Declaration of Bloomon\Bloomapi3\Repositories\User\UserController::store() should be compatible with Bloomon\Bloomapi3\Http\Controllers\BaseController::store(Illuminate\Http\Request $request)', '/home/tom/projects/bloomon/bloomapi3/app/Repositories/User/UserController.php', '6', array('file' => '/home/tom/projects/bloomon/bloomapi3/app/Repositories/User/UserController.php')) in UserController.php line 6
So, how can I type hint inject the UserFormRequest while still adhering to the BaseController's Request requirement? I cannot force the BaseController to require a UserFormRequest, because it should work for any resource.
I could use an interface like RepositoryFormRequest in both the BaseController and the UserController, but then the problem is that Laravel no longer injects the UserFormController through its type hinting dependency injection.
In contrast to many 'real' object oriented languages, this kind of type hinting design in overridden methods is just not possible in PHP, see:
class X {}
class Y extends X {}
class A {
function a(X $x) {}
}
class B extends A {
function a(Y $y) {} // error! Methods with the same name must be compatible with the parent method, this includes the typehints
}
This produces the same kind of error as your code. I would just not put a store() method in your BaseController. If you feel that you are repeating code, consider introducing for example a service class or maybe a trait.
Using a service class
Below a solution that makes use of an extra service class. This might be overkill for your situation. But if you add more functionality to the StoringServices store() method (like validation), it could be useful. You can also add more methods to the StoringService like destroy(), update(), create(), but then you probably want to name the service differently.
class StoringService {
private $repo;
public function __construct(Repository $repo)
{
$this->repo = $repo;
}
/**
* Store a newly created resource in storage.
*
* #return Response
*/
public function store(Request $request)
{
$result = $this->repo->create($request);
return response()->json($result, 200);
}
}
class UserController {
// ... other code (including member variable $repo)
public function store(UserRequest $request)
{
$service = new StoringService($this->repo); // Or put this in your BaseController's constructor and make $service a member variable
return $service->store($request);
}
}
Using a trait
You can also use a trait, but you have to rename the trait's store() method then:
trait StoringTrait {
/**
* Store a newly created resource in storage.
*
* #return Response
*/
public function store(Request $request)
{
$result = $this->repo->create($request);
return response()->json($result, 200);
}
}
class UserController {
use {
StoringTrait::store as baseStore;
}
// ... other code (including member variable $repo)
public function store(UserRequest $request)
{
return $this->baseStore($request);
}
}
The advantage of this solution is that if you do not have to add extra functionality to the store() method, you can just use the trait without renaming and you do not have to write an extra store() method.
Using inheritance
In my opinion, inheritance is not so suitable for the kind of code reuse that you need here, at least not in PHP. But if you want to only use inheritance for this code reuse problem, give the store() method in your BaseController another name, make sure that all classes have their own store() method and call the method in the BaseController. Something like this:
BaseController.php
/**
* Store a newly created resource in storage.
*
* #return Response
*/
protected function createResource(Request $request)
{
$result = $this->repo->create($request);
return response()->json($result, 200);
}
UserController.php
public function store(UserFormRequest $request)
{
return $this->createResource($request);
}
You can move your logic from BaseController to trait, service, facade.
You can not override existing function and force it to use different type of argument, it would break stuff. For example, if you later would write this:
function foo(BaseController $baseController, Request $request) {
$baseController->store($request);
}
It would break with your UserController and OtherRequest because UserController expects UserController, not OtherRequest (which extends Request and is valid argument from foo() perspective).
As others have mentioned, you cannot do what you want to do for a host of reasons. As mentioned, you can solve this problem with traits or similar. I am presenting an alternative approach.
At a guess, it sounds like you are trying to follow the naming convention put forth by Laravel's RESTful Resource Controllers, which is kind of forcing you to use a particular method on a controller, in this case, store.
Looking at the source of ResourceRegistrar.php we can see that in the getResourceMethods method, Laravel does either a diff or intersect with the options array you pass in and against the default values. However, the those defaults are protected, and include store.
What this means is that you can't pass anything to Route::resource to force some override of the route names. So let's rule that out.
A simpler approach would be to simply set up a different method just for this route. This can be achieved by doing:
Route::post('user/save', 'UserController#save');
Route::resource('users', 'UserController');
Note: As per the documentation, the custom routes must come prior to the Route::resource call.
The declaration of UserController::store() should be compatible with BaseController::store(), which means (among other things) that the given parameters for both the BaseController as well as UserController should be exactly the same.
You actually cán force the BaseController to require a UserFormRequest, it's not the prettiest solution, but it works.
By overwriting there is no way you can replace Request with UserFormRequest, so why not use both? Giving both methods an optional parameter for injecting the UserFormRequest object. Which would result in:
BaseController.php
class BaseController {
public function store(Request $request, UserFormRequest $userFormRequest = null)
{
$result = $this->repo->create($request);
return response()->json($result, 200);
}
}
UserController.php
class UserController extends BaseController {
public function __construct(UserRepository $repo)
{
$this->repo = $repo;
}
public function store(UserFormRequest $request, UserFormRequest $userFormRequest = null)
{
return parent::store($request);
}
}
This way you can ignore the parameter when using BaseController::store() and inject it when using UserController::store().
The easiest and cleanest way I found to circumvent that problem was to prefix the parent methods with an underscore. For example:
BaseController:
_store(Request $request) { ... }
_update(Request $request) { ... }
UserController:
store(UserFormRequest $request) { return parent::_store($request); }
update(UserFormRequest $request) { return parent::_update($request); }
I feel like creating service providers is an overkill. What we're trying to circumvent here is not the Liskov substitution principle, but simply the lack of proper PHP reflection. Type-hinting methods is, in itself, a hack after all.
This will force you to manually implement a store and update in every child controller. I don't know if that's bothersome for your design, but in mine, I use custom requests for each controller, so I had to do it anyway.
I'm using latest (dev-master) sonata admin and I want to create my own createAction() method for sonata admin. I have to do that, because I want to save some user information, while adding to database.
My custom controller is - S\CoreBundle\Controller\NewsAdminConroller.php
<?php
namespace S\CoreBundle\Controller;
use Sonata\AdminBundle\Controller\CRUDController as Controller;
use Symfony\Component\Security\Core\SecurityContextInterface;
class NewsAdminController extends Controller
{
/**
* Set the system user ID
*/
private function updateFields($object)
{
//some code - this is my own method
}
public function createAction(Request $request = null)
{
//code for create ... it's almost the same as default code.
}
}
Default CRUD - Sonata\AdminBundle\Controller\CRUDController.php:
class CRUDController extends Controller
{
public function createAction(Request $request = null)
{
//...
}
}
Both createAction() methods have exactly the same arguments, name ...
And it throw's me an error:
PHP Strict Standards: Declaration of S\CoreBundle\Controller\NewsAdminController::createAction() should be compatible with Sonata\AdminBundle\Controller\CRUDController::createAction(Symfony\Component\HttpFoundation\Request $request = NULL) in /home/mark/dev/project/src/S/CoreBundle/Controller/NewsAdminController.php on line 129
The Sonata\AdminBundle\Controller\CRUDController::createAction(Symfony\Component\HttpFoundation\Request $request = NULL)
Needs a Request Object, but if you don't declare it, it point to S\CoreBundle\Controller\Request
Just add
"use Symfony\Component\HttpFoundation\Request;" in top of file.
Update
Since the commit https://github.com/sonata-project/SonataAdminBundle/commit/49557c302346f57d962b83b31e2931446ff60e9c, there is no need to set the request as parameter.
The create Action is only
Sonata\AdminBundle\Controller\CRUDController::createAction()
I'm looking at the Laravel docs, and I see this snippet:
class UserController extends BaseController {
/**
* The layout that should be used for responses.
*/
protected $layout = 'layouts.master';
/**
* Show the user profile.
*/
public function showProfile()
{
$this->layout->content = View::make('user.profile');
}
}
we can clearly see that $this->layout = 'layouts.master'. However, then they define a child of the layout object (which as I understand is only a base PHP string, and does not have a field called content, via ...
$this->layout->content = View::make('user.profile');
How can a string have a field called content defined?
when I subclass BaseController and try to assign a value to
$this->layout->content, why do I get the following error: "Attempt
to assign property of non-object"?
Why not look at BaseController? It looks like they change $this->layout.
https://github.com/laravel/laravel/blob/master/app/controllers/BaseController.php
<?php
class BaseController extends Controller {
/**
* Setup the layout used by the controller.
*
* #return void
*/
protected function setupLayout()
{
if ( ! is_null($this->layout))
{
$this->layout = View::make($this->layout);
}
}
}
My advice, if you use a PHP framework, don't be afraid to check its source when you don't understand why it works. There is usually much less "magic" than you think...
IMO, this seems like a bad design though, to initialize the variable as a string and then change it to be some object. Kind of just abusing the loose typing.
I want to make some action (php script) before all actions in my frontend app and then pass a result from that script to actions in variable - so I can get variable value from all actions. Where should I declare sth like this?
If the filter solution dont feet your needs, you can also create a base action class with a preExecute function:
// app/frontend/lib/baseActions.class.php
class baseActions extends sfActions
{
public function preExecute()
{
$this->myVar = .... // define your vars...
}
}
Then your module actions class extends your baseActions class:
// app/frontend/modules/myModule/actions/actions.class.php
class myModuleActions extends baseActions
{
public function executeIndex(sfWebRequest $request)
{
// var $this->myVar is available in any action and in your template
...
}
}
if you have to use the preExecute function in your module class action, remember to call parent::preExecute() in it.
What kind of information ?
I would recommend you to use filters.
In your apps/frontend/config/filters.yml:
rendering: ~
myfilter:
class: myCustomFilter
Create the file lib/filter/myCustomFilter.php:
<?php
class myCustomFilter extends sfFilter
{
public function execute ($filterChain)
{
if ($this->isFirstCall())
{
// do what ever you want here.
$config = Doctrine_Core::getTable('Config')->findAll();
sfConfig::set('my_config', $config);
}
$filterChain->execute();
}
}
And then, every where, you can retrieve your data:
sfConfig::get('my_config');
How can i forward to other action inside the same controller avoiding repeat all dispatch proccess ?
Example:
If i point to User Controller the default action is indexAction() inside this funciton i use _forwad('list') ... but all dispatch proccess are repeated.. and i dont that
Whats is the right way ?
Usually, you will install routes to redirect your users to the proper (default) action, instead of the index action (read how to redirect from a given route using Zend_Router). But you can do everything manually if you really want to (however this is called "writing hacker code to achieve something dirty") directly from the controller.
Change your "view script" to be rendered, then call your action method....
// inside your controller...
public function indexAction() {
$this->_helper->viewRenderer('foo'); // the name of the action to render instead
$this->fooAction(); // call foo action now
}
If you tend on using this "trick" often, perhaps you may write a base controller that you extend in your application, which can simply have a method like :
abstract class My_Controller_Action extends Zend_Controller_Action {
protected function _doAction($action) {
$method = $action . 'Action';
$this->_helper->viewRenderer($action);
return $this->$method(); // yes, this is valid PHP
}
}
Then call the method from your action...
class Default_Controller extends My_Controller_Action
public function indexAction() {
if ($someCondition) {
return $this->_doAction('foo');
}
// execute normal code here for index action
}
public function fooAction() {
// foo action goes here (you may even call _doAction() again...)
}
}
NOTE : this is not the official way to do it, but it is a solution.
We Can Also use this Helper To redirect
$this->_helper->redirector->gotoSimple($action, $controller, $module, $params);
$this->_helper->redirector->gotoSimple('edit'); // Example 1
$this->_helper->redirector->gotoSimple('edit', null, null, ['id'=>1]); // Example 2 With Params
If you don't want to re-dispatch there is no reason you can't simply call the action - it's just a function.
class Default_Controller extends My_Controller_Action
{
public function indexAction()
{
return $this->realAction();
}
public function realAction()
{
// ...
}
}
You could also create a route. For example I have in my /application/config/routes.ini a section:
; rss
routes.rss.route = rss
routes.rss.defaults.controller = rss
routes.rss.defaults.action = index
routes.rssfeed.route = rss/feed
routes.rssfeed.defaults.controller = rss
routes.rssfeed.defaults.action = index
Now you only need one action and that is index action but the requess rss/feed also goes there.
public function indexAction()
{
...
}