Yii 1.1: Can I have multiple base controllers? - model-view-controller

There is a Controller class in protected/components that starts with the following comment:
/**
* Controller is the customized base controller class.
* All controller classes for this application should extend from this base class.
*/
class Controller extends CController {
...
Can I define an alternative base controller, lets say Controller2:
class Controller2 extends CController {
...
And derive some user controllers from it? Will it violate Yii architecture or introduce some flaws somehow?

Yes, you can have multiple base controllers. Default template represents pretty basic application so some concepts are simplified. In bigger applications having multiple base controllers is pretty common (for example separate modules can have its own base controller), although usually you have some hierarchy and there is one base controller at the top of inheritance tree.
You can have multiple base controllers which extends CController directly, but it may be harder to introduce app-specific behavior - in your case you would need to copy the same code to both Controller and Controller2 classes. So usually it is good practice to create app-level base controller which will be base for all controllers - even if it is empty at the beginning in may save you the trouble of changing each controller at a later stage of the project.
For example you may have separated base controllers for backend and frontend, which extends one app-level base controller:
class FrontendController extends Controller {
// frontend-specific adjustments
}
class BackendController extends Controller {
// backend-specific adjustments
}
class Controller extends CController {
// app-specific adjustments (for backend and frontend)
}

Related

RequestMapping discriminating by user in Spring

I have two controllers that handle the same resource. One for the regular web app and another one for portals (and in theory there could be more than one portal and more controllers).
#Controller
#RequestMapping(value = "/*/web/cases")
public class CasesDetailController extends BaseController<CasesForm> { ... }
#Controller
#RequestMapping(value = "/*/portal/cases")
public class CasesPortalDetailController extends BaseController<CasesForm> { ... }
In some controllers we just resolve a different view for the portal, but for some cases the controller logic is quite different. For example this one has many mappings in the portal that are not available in the app.
The problem comes with managing 2 different urls for the same resource. For example, if I wanna place a link for a case I have to calculate it in the server by checking if the current user is logged in the web or the portal instead of placing a constant base url and an uuid as parameter.
My question is if I can do something about the request mapping to decide which controller can I use. The best I can think of is using a parameter:
#RequestMapping(value = "/*/cases") // Web
#RequestMapping(value = "/*/cases", params = "device=portal") // Portal
But I would like to avoid sending that info in the url when I have it available in the user (we use a custom user class that extends the base user from spring and it contains the device). Is there any way to check that in the request mapping? Something like this:
#RequestMapping(value = "/*/cases", magicFunctionality=getLoggedUser().getDevice()="web")
Probably nothing like this exists (at least I found nothing in the manual) but any ideas of how to face this problem would be wellcome.
Thanks in advance.
Gonzalo.

Sharing modelAttribute between views of different controllers

In my current Spring project (using Thymeleaf), I have this two controllers HomeController and a generic controller extended to all controllers associated to the entities from the model layer.
In the HomeController, i have this modelAttribute mthod:
#ModelAttribute("usuario")
public Usuario usuario() {
return usuario.findBy("username", SecurityContextHolder.getContext().getAuthentication().getName());
}
Which obviously is acessible by the views mapped in this controller. Is there any way of share this modelAttribute with the views mapped in the other controller?
I read in many places about this RedirectAttributes resource, but I am guessing that only works with redirected views (like modelAndView.setViewName("redirect:welcome");)?
Move the method to a separate class and add the #ControllerAdvice annotation to that class. This way all the model attributes in that class will apply to all controllers in the application.
If you want to limit the set of controllers where the model attribute will be available you can specify in the #ControllerAdvice annotation that only controllers in specific packages or with specific annotations or implementing specific interfaces should be enhanced.

How to map a path to multiple controllers?

I'm currently working on a spring based web application and have a special requirement that seems not (at least not out of the box) be provided by spring MVC. The application serves data for multiple users each organized in their own "company". Once a user has logged in, I'm able to identify to which company he belongs to.
The application itself is built with multiple "modules", each with it's own domain objects, DAO, Service and Controller classes. The idea behind this concept is that I can for example extend a certain controller class (let's say to use a different service class) based upon the user and here is my problem.
Since i do not want to change my request paths for certain users, I'm currently looking for a way how to serve a request issued on a certain request path with different instances of a controller based upon the user issuing the request.
I came up with the idea to attach a HTTP Header Field for the company
Example:
X-Company:12345
and have my controllers configured like this:
#Controller
#RequestMapping(value="/foo/")
public class FooController {
// ...
}
#Controller
#RequestMapping(value="/foo" headers="X-Company=12345")
public class SpecialFooController extends FooController {
// ...
}
However this is not possible, since spring MVC treats each header (except Content-Type and Accept) as a kind of restriction, so in my case it would handle all requests with the FooController instead of the SpecialFooController unless i add a "headers" restriction on the FooController as well, which is not practicable.
Is there some way how to customize this behaviour or some direction one could point me to look for? Or maybe someone has another idea how to achieve this. It'll be highly appreciated.
Thanks!
I'am not sure but I think you can do this with HandlerMapping. Have a look at the documentation
To take your own suggestion, you can use the #RequestHeader annotation in your controller methods:
#Controller
public class MyController {
#RequestMapping("/someAction")
public void myControllerMethod(#RequestHeader('X-Company-Id') String companyId) {
}
}
Or you could use #PathVariable:
#Controller
public class MyController {
#RequestMapping("/{companyId}/someAction")
public void myControllerMethod(#PathVariable("companyId") String companyId) {
}
}
Using this approach would mean that it is in fact different URLs for each company, but if you can set the company id header, I guess you also can suffix the URLs with the company id.
But there are also other possibilities. You could write an interceptor that puts the company id in a session or request variable. Then you wouldn't have to add the annotation to every controller method. You could also use a subdomain for each company, but that wouldn't look too pretty if the company id is a random alphanumeric string. E.g: companyone.mydomain.com, companytwo.mydomain.com
Edit
#RequestMapping can be added to the controller level as you know, so you should be able to do
#Controller
#RequestMapping("/controller/{companyId}")
as the base url, if that's a better option.
I was able to meet the requirement by making usage of a customized RequestCondition. By defining your own annotation that can be placed at the type and method level of a controller. Extending the RequestMappingHandlerMapping by your own implementation and overriding the getCustomTypeCondition() and getCustomMethodCondition() methods translates a controller annotation into your own RequestCondition.
When a request comes in, the custom RequestCondition will be evaluated and the annotated controller(method) will then be called to serve the request. However this has the downside, that one needs to remove a servlet-context.xml file and switch to the WebMvcConfigurationSupport class instead in order to be able to use your customized RequestMappingHandlerMapping class.
This question was also discussed here.
Edit:
A pretty good example using this can be found here.

Can a controller use the same instance of command from a controller it extends?

I have multiple SimpleFormController controllers. One controller contains a command object used to filter results in the other controllers. I seem to be missing something simple, but I can't seem to find a way to use the same instance of the command object in the other controllers.
My setup is such that this main controller, let's call it RootController extends SimpleFormController, and the rest of the controllers extend RootController. The idea was that the command object is stored in one place - RootController and the controllers that extend it reuse the same object. However, it doesn't seem to be working that way, other controllers seem to have their own copy of the command object.
Form backing objects are just normal Pojos, so you can inherit it form each other.
public class BaseCommand {
...
}
public class MoreCommand extends BaseComman {
...
}
May you just forget the "update" the commandClass in your Controller Subclasses.
Anyway: notice that SimpleFormController is deprecated in Spring 3.0. Instead the Annotation Style is preferred.
Update: One INSTANCE of an command object, can be handled by only one INSTANCE of an Controller. So you can subclass the Controller (don't miss to call super), but you can not have two instances of the controller and hope that both are invoked.

Cakephp: Abstracting AppController another level, possible?

I was wondering if it's somehow possible to add another abstraction controller between AppController and my app's other controllers?
So that my controllers, e.g. UsersController extends SecureController
and SecureController extends AppController.
Also I want to be able to have other controllers extend AppController directly:
SomeNonSecureController extends AppController.
this is because my current AppController has all sorts of Auth and ACL stuff in its beforeFilter, but i also have controllers that don't need that security stuff (before everything needed the security, no new specs have been added)..
but because some many controllers do need it, it doesn't make sense to copy-paste the code to all needy controllers.
I was thinking to but all the beforeFilter security stuff into a SecureController - that way any controllers that need security simpley extend it, while others inherit from AppController directly.
How would you go on about doing something like this?
Thanks in advance,
Ken.
My first thoughts would be to see if I could abstract some of the functionality from the beforeFilter into a component - remember components can use other components too, just include them in your component's $components property, so you can access the AuthComponent and AclComponent etc.
If this was not suitable then I'd go for your route, in order to do it, just include('secure_controller.php'); before your individual controller class declaration in it's file.
I have done something similar by creating a BaseController that I use in all my projects which provides all my admin CRUD actions that are standard. I then have my AppController extend this which contains application specific, controller wide functionality, then individual controllers extend that, and end up being practically empty. All I do is:
// app/base_controller.php
<?php class BaseController extends Controller {} ?>
// app/app_controller.php
<?php
include('base_controller.php');
class AppController extends BaseController {}
?>
// app/controllers/my_controller.php
<?php class MyController extends AppController {} ?>
I've just been attempting this too. It seems it's relatively simple to extend any controller with any other one. In Cake 2.0 you just use the import() statement (include() does a similar thing).
App::import('Controller', 'Security');
class SecureAreaController extends SecurityController {
// extra functionality *not* in base class goes here
}

Resources