I asked this question previously : Codeigniter Class Inheritance between modules (wiredesigns)
I accepted an answer to that question which provided a way of doing what I want by extending a custom controller in application/core. This approach seemed sensible and offered the added benefit of allowing both modules to function independently.
However, Having thought about it, I am not sure this is the best approach for this situation because My Products controller will require almost all of the functionality of the item controller so this approach will leave my item controller almost empty - I'm not sure I like this.
QUESTION 1
Although I may still go with the approach previously suggested, I would like to know if there is a way to extend classes between modules so that I can weigh up the two options (see original question below)
QUESTION 2
Additionally, I think I should be able to re-use much of my model class, I can see that I would be able to do this using the MY_Model in app/core approach. But...
a) Is there a way to directly access another modules models?
b) Could I extend my models between modules in a similar way to question 1 for controllers
PREVIOUS QUESTION
My CI2 app is using the wiredesigns modular layout.
I have a two modules called item and product in a a folder called modules like so:
/application
/modules
/item
/product
In Item I have a controller called item which starts like this.
class Item extends MX_Controller
{
//code here
}
What do I need to do to make my products controller extend my item controller in a different module
The correct answer here is:
Don't
Why are you trying to do this? Place your logic into a library or some sort of shared file and implement a call to that library instead.
You could make Item an abstract class in your application/libraries folder and the autoloader built into HMVC will automatically load it. There is no reason this needs to be in a module, and it definitely doesn't need to be a controller.
Related
As far as I know, currently Yii 2 doesn't have an out of the box method to resolve ambiguity of controller and module names. An example of module hierarchy to describe what exactly I mean:
app\modules\v1\controllers\UserController // resolves the /v1/users and /v1/users/{id} actions
app\modules\v1\modules\user\Module.php // nested module, resolves the /v1/user/... controllers and their actions, e.g. /v1/user/something/{id}
In this case, the UserController conflicts with the user Module. The main reason of the ambiguity is the singular-plural magic of Yii 2 framework. I didn't find an appropriate solution to resolve this ambiguity. Further my ideas how to resolve it.
Rename the module.
Rename the UserController to the UsersController.
Create an additional submodule, and place there the UserController. E.g. app\modules\v1\modules\root\controllers\UserController
I'm not sure that at least one of these options is a quite elegant one and a proper solution in view of the Yii 2 philosophy.
Coming back to the main question, what is a more appropriate approach to resolve this issue by the Yii 2 philosophy? Controller and Module is two different types of objects, which is differently pluralized or not, thus should be right way to separate them in the routing for the described case.
How I usually deal with this depends a bit on how I'm structuring things.
Let's say I have users, departments, orders and maybe some other stuff. All these concepts have their own module in which all interactions take place. If I want to create REST controllers for all these concepts I can choose to add a controller to every module or I can create a dedicated API module for the controllers.
When creating a dedicated module for this purpose I usually name it api, maybe with some nested versioning module(s) inside. So in this situation I would get the following structure app\modules\api\controllers\UserController which would result in the URL /api/user. No ambiguity there and pretty clear what this is meant for.
When adding such a controller to the module itself I choose a better name than just 'UserController'. When asking myself the question 'What does this controller accomplish?/What does it do?' I find that just UserController doesn't cut it; Especially when inside a User module, resulting in /user/user. This controller is probably going to exist alongside one or more different controllers in the User module, all meant for something different. So, usually, I end up naming it just ApiController, resulting in /user/api. Another controller could be the ProfileController. So when looking at the URLs it's pretty clear what /user/api and /user/profile do. Without the ambiguity.
I am not sure I can fully understand the question, but probably you're asking about classname aliases?
use My\Full\Classname as Another;
Question:
How do I extend a controller of an HMVC module and then call that extended class in Codeigniter's URI Router?
Details:
Assuming the following typical Codeigniter file structure as it relates to HMVC:
/
/application
/application/modules
/application/modules/module-name
/application/modules/module-name/controllers
/application/modules/module-name/controllers/controller-name.php
/application/modules/module-name/models
/application/modules/module-name/models/model-name.php
/application/modules/module-name/views
/application/modules/module-name/views/view-name.php
/application/config
/application/config/routes.php
I have a class Events stored in /application/modules/events/controllers/events.php. I would like to extend it to Courses and Conferences which would provide specific functionality to those respective controllers. I would like to store those controllers in the same module folder (ie. /applications/modules/events/controllers/courses.php, etc.) I would like to route users that visit http://www.example.com/courses to the courses controller like so:
$route['courses'] = 'courses/getList';
I would also like to route users that visit http://www.example.com/conferences to the conferences controller like so:
$route['conferences'] = 'conferences/getList';
The Problem:
Unfortunately, I can't seem to get this working because courses isn't contained in it's own module and therefore can't be found by the router. It appears that the router is looking for the course class file at the following file path: /applications/modules/courses/controllers/courses.php. I have tried move that file to the aforementioned path, but I am then asked for the location of the Events controller class that Courses extends from.
A Possible Solution:
One possible solution might be to contain each of the controllers in a single controller file. However, I do not wish to do this because I like to store each class in it's own individual file and grouped with it's extended classes if at all possible. I am looking for a more elegant and altogether correct solution. Even if I were to attempt this technique of a single file, it would need to be contained in /applications/modules/courses/controllers/courses.php to be accessed. This leaves me out of luck when trying to later route to the conferences controller.
It seems that by adding
require APPPATH."modules/events/controllers/events.php";
to the beginning of my extended classes (courses and conferences) I will be able to leave the Events module alone.
Unfortunately, I have to create separate modules for the courses and conferences controllers. Oh well, I guess it can't be perfect.
What It Is
Here is what I've done so far:
core/
controllers/ (contains the controllers used by the app)
models/ (contains the models used by the app)
views/ (contains the views used by the app)
base_controller.php (the controller every other extend)
base_model.php (the model every other extend)
vendors/
phprouter/ (a simple router class)
pimple/ (a simple DI container class)
configuration.php (contains all the app configuration)
index.php (includes the configuration, vendors, base model, base controller, sets the DI container up and route the request)
See the code here: http://pastebin.com/pxUpUvv6
Please note that the given code is just an example, therefore the controllers, models, views aren't in place yet. Also, it may be buggy—as untested—, but it doesn't matter right now.
Request Flow
The client requests index.php.
The configuration, vendors, base controller, base model are included.
The DI container and the dependencies are initialized, we can now inject them anywhere.
We map controllers to URL and the router does its job.
The controller is fetched (although this is not in the example code, as noted above).
We do some stuff.
The method then calls ::call_model(), which includes the corresponding model from core/models/, and then calls the same method we're using from the model class corresponding.
The model is fetched.
More stuff.
The model then calls ::call_view()', which includes the corresponding view from core/views/.
The view is fetched and render the page to the client.
FYI: Corresponding
Examples of controller, model, view which correspond:
Controller Controller_Products::list() at core/controllers/Controller_Products.php
Model Model_Products::list() as core/models/Model_Products.php
View at core/views/Model_Products_list.php
Issues Being Faced
Actually, I feel a bit uncomfortable with this structure. Dunno, it seems to be far from scalable, modulable...
Does only the basic folder structure—core{, /controllers, /models/, /views}, vendors at the root—looks good to you?
I feel like I should get __autoload() outside of index.php, which seems a little too big to me. If so, what about DI container?
Maybe if I get to needing more than two external library, it should be better not to have them included one by one, manually? But how?
Putting all the configuration in a file configuration.php at the root looks to me like old-fashioned PHP4. Thanks to the power of Pimple, I could embed this configuration directly into it but yet, where?
I think the way I handle ::call_model() (core/base_controller.php) and ::call_view() (core/base_model.php) is a bit awkward. Would you agree? What'd be a simplified way to redo the whole thing?
Considering all my issues, would it eventually be better for me to use a framework as Symfony?
If something isn't clear, feel free to ask.
Thanks.
Yes.
You can use autoload and DI container together. There is example, how autoload can be used with naming convention. I recommend to use spl_autoload.
With autoload you can remove all (or almost all) includes.
In index.php, I guess.
Yes, it's wrong way. First of all, try to not use static methods. Also, models should have methods with descriptive names, not just 'call me and I will do all what I can'. It's more complex problem - you need to understand how Controller and Model should do their cooperation. As variant, read some books. Controller should call methods of Model, to get data for some situation. Model it's not just place for code of controller. Different controllers can use different models. Models too can use another models.
Answer to this question can not be objective :)
I have few instances of Zend_Acl objects, like this one (one for each module):
class My_Acl_Module1 extends My_Base_Acl
{
public function __construct()
{
parent::__construct();
$this->addResource('News_Model_Entry');
$this->deny('guest', 'News_Model_Entry', 'index', new News_Model_Acl_Assert_CategoryPrivate());
}
}
class My_Base_Acl extends Zend_Acl
{
public function __construct()
{
$this->addRole('guest');
}
}
How to merge them into one ACL object to use in navigation container?
Edit
Some more info:
I don't use controller resources in favor of model based resources (models, forms implement Zend_Acl_Resource_Interface). All of them have method isAllowed()
I use modular directory and reusable modules (separate models, configs, routes etc.)
My application knows all installed modules, controllers, action (structure already parsed, not in real time)
So I'm looking the way to follow this scheme, and separate the ACL for each module. I don't want to use application resource, because it is a waste - acl is not needed always.
I have an action helper which instantiates module specific ACL only when it is really needed. But sometimes I'd like to have global application ACL available too (eg. when I'd like to pass it to the navigation view helper or controller plugin).
My module ACL classes have all just one method: init().
Dirty solution I see, is to parse the source classes and merge the files into one method for new class.
Any suggestions?
I think this is almost impossible without the application knowing a bit more about itself. I had a similiar problem a while ago too and didn't find any satisfying solution. When merging you'll loose some information (the module) which you need later on in your navigation.
The first solution I had back there, was to iterate over all acl-files in each module and create a custom ACL-merge function to merge them all. This actually did work but I didn't like the idea of a file-scan over my complete application (even if the results were cached).
My last approach was to add more data to my application: For every linkable action I defined the corresponding acl-file. This wasn't as much work as it may sound. Mainly because my controllers/models do match almost exactly. So I have a model 'News' and a controller 'News' which handles the access to it and maps every action of the model to the frontend. So I only had to specify the acl/controller relations. (I used a custom element in each navigation container to save this).
Suppose you are building a web application that is going to be a packaged product one day, one that users will want to be able to extend and customize.
It comes with a core library consisting of PHP files containing classes:
/library/
/library/frontend.class.php
/library/filesystem.class.php
/library/backend.class.php
Now, suppose you want to keep a clean core that users can't patch. Still, you want the user to be able to customize every nut and bolt if need be.
My current idea is to create an autoloading mechanism that, when a class is instantiated, first loads the core include:
/library/frontend.class.php
then, it switches to the user directory and looks whether there is an include of the same name:
/user/library/frontend.class.php
if one exists, it includes that as well.
Obviously, the user include must contain a class definition that extends the definition in the core include.
Now my question is, how would I instantiate such a class? After all, I can always be sure there is a definition of:
class frontend_core
but I can not be sure there is a
class frontend_user extends frontend_core
However, I would like to be able to rely on, and instantiate, one class name, regardless of whether there was a custom extension to the class or not.
Is there a clever way, idea, or pattern how to achieve this?
Of course, I could write a simple factory helper function that looks for the user class first and then for the core class and returns an initialized object, but I would really like to keep this as clean and simple as possible, because as I said, it is going to be a packaged product.
I am looking for a smart trick or pattern that uses as little code, and introduces as little new functionality, as possible.
Why don't you follow the approach as used by Propel? You generate your base classes and already provide an empty User class (extending the base class) where your users can put their overrides/specific implementation details, and in your code you always refer to the User classes. So basically you just use the inverse of the logic you described.
If the explanation above isn't clear, check out http://propel.phpdb.org/trac/wiki/Users/Documentation/1.4/QuickStart#a6.UsingtheGeneratedSQLandOMFiles and generate code for a small database. The base classes are in the om folder, the (by default empty) user classes are in the root folder.
I would implement hooks in the core, so users dont have to hack the core, but are still able to extend the core using hooks
I'd go with using the constructor of the core class to determine the user class to load, and then implement a factory method in the core class to generate instances of the user class. By making the constructor of the user class protected, and having the user class extend the core class you can be sure that code elsewhere cannot instantiate the user class.
C.
I think it's more complicated with a single filename when you want to use inheritance as well. Basically class user_frontend extends core_frontend has to know where to find both classes. Both must be included.
If you just want to do new Frontend you could use PHP5.3's class_alias to point Frontend to the main class to use. Below 5.3. you could use a ServiceFinder, that knows how to map Service Names to Classes and then get the Frontend with $service->get('frontend') or use a Dependency Injection framework.
Edit I removed the Loader code given before, because it was suffering from exactly this problem.
You could have a loader class that will decide which class to instance:
Loader::instance()->load('Frontend')