Jump to content

Archived

This topic is now archived and is closed to further replies.

obsidian

How do YOU code modularly?

Recommended Posts

OK, guys, here's a question I've been wanting to ask a few of you for a while: how do you go about designing and implementing a modular code design to a project? I'm not talking about simply breaking things down into classes and using OOP principles: I've got that covered. What I want to know is this: when you have a project that you want to write a bare bones application for, knowing that you will be adding modules to it later, how do [b]you[/b] design your application?

I've pulled a few things apart to get some ideas, but rather than muddying the waters with my thoughts, I wanted to ask how some of you all approach the task. Let me give you a simple example that we might be able to talk through and get a feel for things. Let's assume I'm writing a full text parser for a bulletin board or blog, and I want the option of adding future parsing modules to my overall text parser. I don't want to have to add any code to my existing Parser class to pickup the additional functionality, but I do want it to be able to pick up any modules in the "plugins" folder and use them as part of its own Parse() function.

Anyone want to attempt an explanation of how [b]you[/b] would tackle such a problem? I've got a few projects that this would be extremely helpful for, and as I'm planning the layout of them, I wanted to get some input from the community before I laid my framework out and tied my own hands with it.

Thanks in advance for any input.

Share this post


Link to post
Share on other sites
You say not by using OOP principles, but if you're coding OO, what other way is there to do it?

Abstraction and polymorphism are KEY to modularity. If you know you'll be adding functionality later, create an abstraction layer, even if it doesn't seem to serve any purpose at this time.

Share this post


Link to post
Share on other sites
I would do it something like this. First, create the all-encompassing parser class.

[code]<?php
class text_parser
{
private $modules;
private $originalText;
private $parsedText;

public function __construct()
{
$this->getModules();
}

public function getModules()
{
$modules = array("nl2br");
//This could be anything. Read a text file, select from a database, or just get the contents of the modules directory and go from there. At any rate:
foreach($modules as $module)
{
$module = "text_module_$module";
$this->modules[] = new $module($this);
}
}

public function getOriginalText()
{
return $this->originalText;
}

public function parse()
{
foreach($this->modules as $module)
{
$this->originalText = $module->parse();
}
return $this->originalText;
}

}
?>[/code]

Then a basic module class:

[code]<?php
abstract class text_module_base
{
private $parent;

public function __construct($parent)
{
$this->parent = $parent;
}

abstract public function parse();
}
?>[/code]

And now we're ready to start creating fully modular... umm... modules. For this example, we'll create a parser to convert newline characters to an HTML line break. Yes, I know, there's already nl2br(). Stop complaining.

[code]<?php
class text_module_nl2br extends text_module_base
{

public function __construct($mainParser)
{
parent::__construct($mainParser);
}

public function parse()
{
return str_replace("\n", "<br />\n", $parent->getOriginalText());
}
}
?>[/code]

In this example, every time you add a module, you just add the last part to the array $modules in the main parser class. However, with time and added modules, this would get messy quickly. For this reason, I suggest using a config file or database entries, or some way of automatically pulling every module in the modules directory.

Note: In order for

[code]foreach($modules as $module)
{
$module = "text_module_$module";
$this->modules[] = new $module($this);
} [/code]

this to work, you should have a function like this somewhere, probably in the controller part of your MVC system.

[code]public function __autoload($class)
{
$class_name = str_replace("_", "/", $class).".php";
require_once($class_name);
}[/code]

Adapt to your system, directory delimiters in particular. This makes sure that any classes you request will be included or required.

Share this post


Link to post
Share on other sites
[quote author=448191 link=topic=113609.msg461779#msg461779 date=1162486285]
You say not by using OOP principles, but if you're coding OO, what other way is there to do it?

Abstraction and polymorphism are KEY to modularity. If you know you'll be adding functionality later, create an abstraction layer, even if it doesn't seem to serve any purpose at this time.
[/quote]

Re-read my post. I said I'm [b]already using[/b] OOP. I'm asking how to apply those principles I'm already using to building a modularized application.

Share this post


Link to post
Share on other sites
[quote author=obsidian link=topic=113609.msg461803#msg461803 date=1162487359]
[quote author=448191 link=topic=113609.msg461779#msg461779 date=1162486285]
You say not by using OOP principles, but if you're coding OO, what other way is there to do it?

Abstraction and polymorphism are KEY to modularity. If you know you'll be adding functionality later, create an abstraction layer, even if it doesn't seem to serve any purpose at this time.
[/quote]

Re-read my post. I said I'm [b]already using[/b] OOP. I'm asking how to apply those principles I'm already using to building a modularized application.
[/quote]

I apologize, I misunderstood.

Share this post


Link to post
Share on other sites
Thanks for that, neylitalo. I've used some of those principles before, but I haven't put it all together quite like that. I do, indeed, have use of the __autoload() function. However, I typically have a config file that first includes all modules in the modules directory that handles the includes for me. I guess that just goes back to my trying to make things compatible with PHP4.

Anyone else have other thoughts to add?

[quote author=448191 link=topic=113609.msg461815#msg461815 date=1162487914]
I apologize, I misunderstood.
[/quote]

No harm done ;)

Share this post


Link to post
Share on other sites
[quote author=obsidian link=topic=113609.msg461853#msg461853 date=1162491708]
No harm done ;)
[/quote]

None intended.

On topic:
I personally like to keep track of modules in an XML file. I don't usually pass the top layer object to the module though, as I like to look at the module as a "stand alone" tool. I'll have the top layer class decide what the modules universally need to do.

[code]<?php
interface Parser {
public function parse($string);
}
class TextParser implements Parser 
{
private $modules;

public function __construct(){
$this->getModules();
}
public function getModules(){
$modules = array("nl2br");
foreach($modules as $module){
$this->modules[] = new $module;
}
}
public function parse($input) {
foreach($this->modules as $module){
$input = $module->parse($string);
}
return $input;
}
}
class Nl2brParser implements Parser
{
public function parse($string) {
return str_replace("\n", "<br />\n", $string);
}
}
?>[/code]

Share this post


Link to post
Share on other sites
[quote author=448191 link=topic=113609.msg461881#msg461881 date=1162494041]
I personally like to keep track of modules in an XML file. I don't usually pass the top layer object to the module though, as I like to look at the module as a "stand alone" tool. I'll have the top layer class decide what the modules universally need to do.
[/quote]

Hmm... very interesting approach with the XML reference. I like that idea, especially since the XML file can be generated via PHP from a database, so you can have a user actually install modules in a CMS that way. This is definitely getting closer to the type of things I'm after.

Anyone else?

Share this post


Link to post
Share on other sites
[quote author=obsidian link=topic=113609.msg461905#msg461905 date=1162495397]
Hmm... very interesting approach with the XML reference. I like that idea, especially since the XML file can be generated via PHP from a database, so you can have a user actually install modules in a CMS that way.
[/quote]

That is exactly the thought behind it. Although technically I wouldn't call it a CMS, but more of a "website engine". A CMS would be a module itself. I'm working on it, I hope to be able to share it with ya'll before the end of the year.

Share this post


Link to post
Share on other sites
Interface Driven Design is key to plugins.

btw, when you say XML file from database.. are you actually putting into a flat file, then parsing? Why not just parse it as a string?

Share this post


Link to post
Share on other sites
[quote author=Jenk link=topic=113609.msg462012#msg462012 date=1162505518]
Interface Driven Design is key to plugins.
[/quote]

What's that? We actually agree on something? You sick?

[quote author=Jenk link=topic=113609.msg462012#msg462012 date=1162505518]
btw, when you say XML file from database.. are you actually putting into a flat file, then parsing? Why not just parse it as a string?
[/quote]

??? I can't be a 100% percent sure of obsidians interpretation, but I store/retrieve config options directly from an XML file, no RDBMS involved. If that's what you're saying, otherwise pardon my ignorance.

Share this post


Link to post
Share on other sites
I don't disagree for the fun of it, only when someone is wrong.

[quote]especially since the XML file can be generated via PHP from a database[/quote]that bit (from obsidian) is why I asked about XML.

Share this post


Link to post
Share on other sites
[quote author=Jenk link=topic=113609.msg462073#msg462073 date=1162511120]
[quote]especially since the XML file can be generated via PHP from a database[/quote]that bit (from obsidian) is why I asked about XML.
[/quote]

I'm not sure exactly what I meant by that, to be honest. I initially meant to dynamically generate an XML file like I do with my live RSS feeds, but the more I think about it, that's an entirely unnecessary step if I'm storing my modules that are installed in a database. I guess it's more of an either/or thing. However, if I want to come up with extreme flexibility, I could have the base app pull all the modules through an XML parser and leave it up to the user to define whether that is a static XML file or a dynamically generated XML file.

Share this post


Link to post
Share on other sites
To further elaborate on how I keep track of modules:

I have a 'config' object, wich handles writing and retrieving of configuration options in an XML file using DOM. It's a singleton. It has the available modules as a property.

How that would look in the example:

[code]<?php
public function getModules(){
$config = Config::getInstance();
foreach($modules as $config->modules[__class__]){
$this->modules[] = new $module;
}
}
?>[/code]

Example XML structure:
[code]<modular id="TextParser">
<module id="Nl2Br"/>
</modular>[/code]

Share this post


Link to post
Share on other sites
I hope you don't mind answering this newb question, but I'm a bit confused on the following bit of code:

[code]
<?php
public function parse($input) {
  foreach($this->modules as $module){
      $input = $module->parse($string);
  }

  return $input;
}
?>
[/code]

I know it's recursive, but I can't see what it actually does.  So, uh...what does it do?

Share this post


Link to post
Share on other sites
It's not recursive. The second parse method is the modules' method.

Share this post


Link to post
Share on other sites
[quote author=448191 link=topic=113609.msg462496#msg462496 date=1162579287]
It's not recursive. The second parse method is the modules' method.
[/quote]

??

Okay, now I'm completely lost.

Share this post


Link to post
Share on other sites
[quote author=Nightslyr link=topic=113609.msg462505#msg462505 date=1162580564]
[quote author=448191 link=topic=113609.msg462496#msg462496 date=1162579287]
It's not recursive. The second parse method is the modules' method.
[/quote]

??

Okay, now I'm completely lost.
[/quote]

Take a look at the line that [i]appears[/i] to be recursive. It's actually calling:
[code]
<?php
// this
$module->parse();

// not this (recursive)
$this->parse();
?>
[/code]

It is calling the parse() function of each module you have loaded.

Share this post


Link to post
Share on other sites
[quote author=obsidian link=topic=113609.msg462511#msg462511 date=1162581079]
[quote author=Nightslyr link=topic=113609.msg462505#msg462505 date=1162580564]
[quote author=448191 link=topic=113609.msg462496#msg462496 date=1162579287]
It's not recursive. The second parse method is the modules' method.
[/quote]

??

Okay, now I'm completely lost.
[/quote]

Take a look at the line that [i]appears[/i] to be recursive. It's actually calling:
[code]
<?php
// this
$module->parse();

// not this (recursive)
$this->parse();
?>
[/code]

It is calling the parse() function of each module you have loaded.
[/quote]

With each defined in its own class?

Share this post


Link to post
Share on other sites
Heh, long time since I've shown my face here..

In my experience with writing applications using plugins and modules, the best way takes a couple things:

1) Create a plugins base class to organize/manage them
2) Abstract everything you can to give the plugins easy access to everything
4) Keep your abstracted classes in a single, master class, a registry if you will: $masterclass->someClass;
3) Hooks. Have application hooks placed in your code. This way you can (for example) in your script (Using tip from #4): $masterclass->hooks->add('post_submition'); put that where posts are submitted, for example. Then in the plugin do.. $masterclass->hooks->catch('post_submition', 'function'); so your function is called when that hook is called/created.

Share this post


Link to post
Share on other sites
@ itrebal:

That is more or less how ZF does it for frontcontroller plugins.

1. Plugin broker (registry function)
2. Plugin abstract
3. Plugin broker (broker, decoupling of methods)
4. RouteStartup, PreDispatch, etc. (events)

[b]UML diagram[/b]
[img]http://home.orange.nl/lekkage/img/zf_controller_plugin.png[/img]

[b]Simplified frontcontroller code:[/b]
[code]<?php
class Zend_Controller_Front
{
    private $_plugins = null;

private function __construct()
{
    $this->_plugins = new Zend_Controller_Plugin_Broker();
}
public function registerPlugin(Zend_Controller_Plugin_Interface $plugin)
{
    $this->_plugins->registerPlugin($plugin);
    return $this;
}
    public function unregisterPlugin(Zend_Controller_Plugin_Interface $plugin)
    {
        $this->_plugins->unregisterPlugin($plugin);
        return $this;
    }
public function dispatch()
{
    try {
        // notify plugins that the router is startup up
            $this->_plugins->routeStartup();
            // notify plugins that the router is shutting down
            $action = $this->_plugins->routeShutdown($action);
            // notify plugins that the dispatch loop is starting up
            $action = $this->_plugins->dispatchLoopStartup($action);
            while ($action instanceof Zend_Controller_Dispatcher_Token) {
                // notify plugins that a dispatch is about to occur
                $action = $this->_plugins->preDispatch($action);
               
                $action = $this->getDispatcher()->dispatch($action);
               
                // notify plugins that the dispatch has finish
                $action = $this->_plugins->postDispatch($action);
            }

            // notify plugins that the dispatch loop is shutting down
            $this->_plugins->dispatchLoopShutdown();
    } catch (Exception $e) {
throw $e;
    }
}
}
?>[/code]

Note that this does require to "manually" register plugins (i.e. [i]registerPlugin($plugin)[/i]), unlike with the addition of a plugin "fetcher" (don't know what the correct term should be), where the available plugins are determined by either directorystructure or configuration file, and then automagicly added to the plugin broker.

Share this post


Link to post
Share on other sites
[quote author=448191 link=topic=113609.msg474381#msg474381 date=1164610745]
Note that this does require to "manually" register plugins (i.e. [i]registerPlugin($plugin)[/i]), unlike with the addition of a plugin "fetcher" (don't know what the correct term should be), where the available plugins are determined by either directorystructure or configuration file, and then automagicly added to the plugin broker.
[/quote]

Proposed solution: A single list of plugins, kept by key of broker name. The front controller instantiates it before all else. You should still be able to disable plugins without removing them from the XML file (unregister vs remove).

[img]http://home.orange.nl/lekkage/img/bb_controller_plugin.png[/img]

Share this post


Link to post
Share on other sites
Thought about it some more, and here's what I've come up with (pardon my UML, I'm still learning):

[img]http://home.orange.nl/lekkage/img/bb_plugin.png[/img]

I see one downside to this though: What if I want to use plugins for the classes that manage the plugin lists,
like BB_Config_XML and parent classes?  ???

[b]Edit: [/b]Visibilty is incorrect here and there and BB_Plugin::_plugins should be static.. Sorry..  :P

It would probably be easier to use directory and file naming conventions to list and group plugins. But then you lose the functionality to disable plugins by removing their XML reference.

Share this post


Link to post
Share on other sites
I haven't spent much time with the Zend Framework, but in the time I did spend with it I didn't like it a whole lot. Though, the methods and design they employed did please me quite a lot, and the registry is what I meant with #1 and #4. [I just noticed the list was a bit out of order...]

One solution for example would be to initiate the BB_Config_XML to tell the class what to do, and then pass it to the broker, and that would prevent it from re-initializing it. Not exactly the most elegant, though it would work.

Share this post


Link to post
Share on other sites
I'd just like to thank you guys for this thread as I am in relatively the same position as obsidian right now. These diagrams and examples are very helpful.

Share this post


Link to post
Share on other sites

×
×
  • Create New...

Important Information

We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.