SitePoint Sponsor

User Tag List

Page 1 of 2 12 LastLast
Results 1 to 25 of 32
  1. #1
    SitePoint Wizard gold trophysilver trophy
    Join Date
    Nov 2000
    Location
    Switzerland
    Posts
    2,479
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)

    OO PHP App Design

    Hoping someone can give me some ideas on general OO strategy (which will hopefully help others too) for building PHP apps.

    Working with a general idea of an n-tier design, I think I've got some idea of how to implement content (data) and logic layers in a PHP app and how they should interface. What I'm stuggling with is how to build the user interface (presentation) layer and the best ways to sit that on top of the logic.

    Doing something I generally loath, gonna post a bunch of code, as it speaks for itself. The code is for some kind of content application. I've just knocked it together and haven't tested it - not looking for code re-writes; just suggestions on where to go with and what can be improved.

    OK. First, sitting above PEARS DB class;

    PHP Code:
    <?php
    /* ItemContent.php */

    class ItemContent {

        
    /* Parameters for the Data Access Object */
        
    var $dao;

        
    /* Set up db connection */
        
    function ItemContent() {
            
    $dbtype="mysql";
            
    $dbhost="localhost";
            
    $dbname="name";
            
    $dbuser="user";
            
    $dbpass="password";

            
    /* Define PEAR DB dsn */
            
    $dsn "$dbtype://$dbuser:$dbpass@$dbhost/$dbname";
            
            
    /* Connect to DB */
            
    $this->dao=DB::connect($dsn);

            if (
    DB::isError($this->dao)) {
                die (
    $this->dao->getMessage());
            }        
        }

        
    /* Run SELECTS on db */
        
    function makeSelect($query) {
            
    $result=$this->dao->query($request);
            if (
    DB::isError($result)) {
                die (
    $result->getMessage());
            }
            while (
    $row=$result->fetchRow(DB_FETCHMODE_ASSOC)) {
                
    $data[]=$row;
            }
            return 
    $data;
        }

        
    /* For INSERT, UPDATE, DELETE */
        
    function makeChange($query) {
            
    $result=$this->dao->query($request);
            if (
    DB::isError($result)) {
                die (
    $result->getMessage());
            }
            return 
    true;
        }

        
    /* Lists items */
        
    function listItems() {
            
    $query="SELECT * FROM items";
            return 
    $this->makeSelect($query);
        }
        
        
    /* SELECTS single items */
        
    function getItem($article_id) {
            
    $query="SELECT * FROM items WHERE item_id='".$article_id."'";
            return 
    $this->makeSelect($query);
        }

        
    /* For INSERTS/UPDATES items */
        
    function putItem($type,$elements,$item_id=NULL) {
            
    $i=1;
            
    $j=count($elements);
            foreach (
    $data as $column => $value) {
                
    $value=
                if(
    $i==$j){
                    
    $query.=$column."='".$value."'";
                } else {
                    
    $query.=$column."='".$value."', ";
                }
            }            
            if (
    $type=="update") {
                
    $query="UPDATE items SET ".$query." WHERE item_id='".$item_id."'";
            } else {
                
    $query="INSERT INTO items SET ".$query;
            }
            return 
    $this->makeChange($query);
        }    

        
    /* Helper method to add slashes if magic_quotes is off
           Should this be here or elsewhere - eg. in form validation?
        */

        
    function myAddSlashes ($string) {
            if (
    get_magic_quotes_gpc()==1) {
                return ( 
    $string );
            } else {
                return ( 
    addslashes $string ) );
            }
        }
    }
    ?>
    Sitting above that, the application logic.

    PHP Code:
    <?php
    /* ItemLogic.php */

    class ItemLogic {

        var 
    $itemContent;
        var 
    $template_path;

        function 
    ItemLogic(&$itemContent) {
            
    $this->itemContent=&$itemContent;
            
    $this->template_path="/home/user/www/templates/";
        }

        function 
    showHeader(){
            include ( 
    $this->template_path."html/header.php" );
        }

        function 
    showFooter(){
            include ( 
    $this->template_path."html/footer.php" );
        }
    }

    /* Lists all items */
    class ListItems extends ItemsLogic {

        function 
    ListItems( &$itemContent ) {
            
    ItemLogic::ItemLogic$itemContent );
        }

        function 
    displayItems($resultSet) {
            include ( 
    $this->template_path."html/displayItems.php" );
        }

        function 
    display() {
            
    $items=$this->itemContent->listItems();
            
    $this->displayItems($items);
        }
    }

    /* Gets a single item */
    class GetItem extends ItemsLogic {

        function 
    GetItem( &$itemContent ) {
            
    ItemLogic::ItemLogic$itemContent );
        }

        function 
    displayItem($resultSet) {
            include ( 
    $this->template_path."html/displayItem.php" );
        }

        function 
    display() {
            
    $item=$this->itemContent->getItem($item_id);
            
    $this->displayItem($item);
        }
    }

    /* Shows form for UPDATING OR INSERTING items */
    class EditItem {

        var 
    $item_id;
        var 
    $itemData;

        function 
    ListItem( &$itemContent,$item_id=NULL ) {
            
    ItemLogic::ItemLogic$itemContent );
        }

        function 
    editForm($itemData=NULL) {
            include ( 
    $this->template_path."html/editForm.php" );
        }

        function 
    display() {
            
    /* If editing, fetch item data */
            
    if(isset($this->item_id){
                
    $this->itemContent=$this->itemContent->getItem($this->item_id);
            }
            
    $this->editForm($this->itemData);
        }
    }

    /* INSERTS / UPDATES and shows response */
    class UpdateItem {

        var 
    $item_id;
        var 
    $updateFields;

        function 
    ListItem( &$itemContent,$formData,$item_id=NULL ) {
            
    ItemLogic::ItemLogic$itemContent );

            
    $this->item_id=$item_id;

            
    $acceptedFields=array("Title","Body");
            foreach(
    $formData as $name => $value) {
                if (!
    in_array($value,$acceptedFields)) {
                    
    $this->updateFields['name']=$value;
                }
            }
        }

        function 
    formResponse() {
            include ( 
    $this->template_path."html/formResponse.php" );
        }

        function 
    display() {
            
    $this->itemContent=$this->itemContent->putItem(
                                                           
    $this->formData,
                                                           
    $this->item_id
                                                          
    );
            
    $this->formResponse();
        }
    }
    ?>
    On top of that, the script users will access, acting as some kind of "fusebox";

    PHP Code:
    <?php
    /* index.php */

    /* Include the PEAR DB class */
    require_once 'DB.php';

    /* Include Items Content */
    require("CONTENT/ItemContent.php");

    /* Instantiate the data class */
    $itemContent = new ItemContent();

    /* Include Item Logic */
    require("LOGIC/ItemLogic.php");


    switch ( 
    $_GET['event'] ) {
        case 
    "formResponse":
            
    $itemApp = new UpdateItem($_POST,$_GET['item_id']);
            break;
        case 
    "updateForm":
            
    $itemApp = new EditForm$articleData,$_GET['item_id'] );
            break;
        case 
    "insertForm":
            
    $itemApp = new EditForm$articleData );
            break;
        case 
    "getItem":
            
    $itemApp = new GetItem$itemContent );
            break;
        default:
            
    $itemApp = new ListItems$itemContent );
            break;
    }

    /* Present the interface */
    $itemApp->showHeader();
    $itemApp->display();
    $itemApp->showFooter();
    ?>
    Some questions;

    1. In this design, index.php acts as a proxy for the underlying classes. Is this the best way to do things? What other alternatives exist to make OO PHP apps tick?

    2. In ItemLogic.php, I'm including PHP files which are HTML mixed with a little PHP. Is this the right place to be doing this? What alternative approaches are there to templating for displaying the user interface and how roughly can they be implemented in this design?

    3. Perhaps following on from question 2, how are interface "widgets" (e.g. form controls) usually implemented in this kind of design?

    4. What's approaches are there to supply user navigation around the application. For example, with the current design, say the displayItem.php template contains a link to allow users to edit an item;

    PHP Code:
    <?php 
    /* displayItem.php */
    ?>
    <p><a href="<?php echo($_SERVER['PHP_SELF']);?>?event=updateForm&item_id=<?php echo($_GET['item_id']);?>">Edit Item</a>

    <h2><?php echo ($resultSet['title']);?></h2>

    <pre><?php echo ($resultSet['body']);?></pre>
    Here it's the template which knows about the "context" of this part of the user interface, relative to the rest of the app. That doesn't make me happy.

    Otherwise, any other feedback greatly appreciated.

  2. #2
    Node mutilating coot timnz's Avatar
    Join Date
    Feb 2001
    Location
    New Zealand
    Posts
    516
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Vincent isn't going to like you using the PEAR DB class.
    Oh no! the coots are eating my nodes!

  3. #3
    SitePoint Wizard gold trophysilver trophy
    Join Date
    Nov 2000
    Location
    Switzerland
    Posts
    2,479
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Personally I like PEARs DB class - for a simple abstract db class it does what it's supposed to - one of PEARs stronger classes. Don't think PEAR is all bad - some of what they're doing, such as the SOAP class, is excellent (XML-RPC isn't so good though - uses the Useful Inc class which has some fundamental flaws like it doesn't support variable reflection and it ties you to it's own HTTP client). But anyway - I digress.

    Been thinking about how to approach the navigation system and things like form controls.

    For navigation, thinking a class which keeps track of the URI ($_GET variables) and is instantiated in the ItemLogic base class. From within the templates, rather than hard coding links, call methods from the navigation class.

    Funnily enough, I look at the documentation of a certain class library and find this...

    For things like form controls, create the relevant classes, instantiate them within the extended classes of ItemLogic then place the controls by calling methods in the templates.

  4. #4
    SitePoint Evangelist
    Join Date
    Oct 2001
    Posts
    592
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Personally I like PEARs DB class - for a simple abstract db class it does what it's supposed to
    Except that it isn't simple... Have you looked at the amount of code that's necessary to be able to use PEAR DB? It's humongous. Not only that, but the internal design is wrong. I know that doesn't matter to you, but it does to me.

    If you look at some other, unnamed class library (the one you found some documentation for ), you'll see that the Database classes look a bit like PEAR's, but only on the outside. On the inside it uses much less code and is much simpler.

    By the way, I'll be posting some comments on the rest of your design later on. Right now I don't have too much time available (gotta present and defend my graduation project tomorrow...)

    Vincent

  5. #5
    purple monkey dishwasher scoates's Avatar
    Join Date
    Nov 2001
    Location
    Montreal
    Posts
    794
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Not that I'm trying to pick a fight, of course, but:

    Except that it isn't simple... Have you looked at the amount of code that's necessary to be able to use PEAR DB? It's humongous.
    uh..

    PHP Code:
    require("DB.php");
    $DB DB::connect"mysql://sean:@localhost/some_db" );
    $DB->fetchmode DB_FETCHMODE_ASSOC;
    $sql "SELECT colours FROM colour_table ";
    if ( 
    DB::isError$clrQ $DB->query($sql))) {
        die(
    DB::errorMessage($clrQ) .'<hr size="1"><pre>'$sql .'</pre>');
    } else {
        while (
    $scheme $clrQ->fetchRow()) {
            echo 
    $scheme['colours'];
        }

    although this (obviously) doesn't use all features or anything, it's still functioning PEAR::DB code. Connect, Initialization, Query, Error Check, Iterate and Output in 11 lines, and only slightly more complicated than my "real" DB class (which does it's own error checking/debugging).

    Not only that, but the internal design is wrong. I know that doesn't matter to you, but it does to me.
    I haven't delved very far into it, but you're probably right here. I'm not recommending the PEAR::DB class. Just defending it (somewhat). It's not ALL bad.

    I haven't yet tried ADODB.

    S

  6. #6
    SitePoint Wizard gold trophysilver trophy
    Join Date
    Nov 2000
    Location
    Switzerland
    Posts
    2,479
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    May swap to the Eclipse abstract db. Problem right now is Oracle support (lack of) - got some other feedback (been having a play) which I'll drop you by PM some time soon.

    Meanwhile, good luck in the defence - reckon you'll do well.

  7. #7
    No. Phil.Roberts's Avatar
    Join Date
    May 2001
    Location
    Nottingham, UK
    Posts
    1,142
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    I suggest trying ADODB, it beats the pants off PEAR in terms of speed and functionality....
    THE INSTRUCTIONS BELOW ARE OLD AND MAY BE INACCURATE.
    THIS INSTALL METHOD IS NOT RECOMMENDED, IT MAY RUN
    OVER YOUR DOG. <-- MediaWiki installation guide

  8. #8
    SitePoint Member
    Join Date
    Jun 2002
    Posts
    14
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Sorry this doesn't actually answer your question, but I would suggest you use Eclipse rather than PearDB, its much better organised and better written.

  9. #9
    SitePoint Evangelist
    Join Date
    Oct 2001
    Posts
    592
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    LOL

  10. #10
    midnight coder
    Join Date
    Dec 2000
    Location
    The flat edge of the world
    Posts
    838
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    think voostind was referring to the code INSIDE the class being humungous, not the code require to use it.

    While PEAR's mysql.php file is 26.9KB, the "unnamed class library" sits at 4.57KB, less than 10KB altogether if you include the iterators, etc. And it does exactly what it was suppose to, use the class and you won't need to recode your site when you switch to a different DB.
    Work smarter, not harder. -Scrooge McDuck

  11. #11
    Talk to the /dev/null Theiggsta's Avatar
    Join Date
    Mar 2001
    Location
    Tampa, FL
    Posts
    376
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    PEAR isint good at all, when learning how to use it and all the code needed for it, I passed out while reading the documentation.

    I have my own home brewed one thats 200 lines, has handy functions and error handling which works and thats why I use it. I am actually moving over to PostgreSQL mainly because its a better DB than MySQL (There is so much to explain behind this, but overall for the things I need it to do its best for the job).

    As for the OOP there, man would I get lost in all the objects created, all I do is make a functions object and separate the needed code into its own module and section, no need to complicate things even further with craploads of extra code.
    Aaron "Theiggsta" Kalin
    Pixel Martini
    Ruby and Rails Developer

  12. #12
    As the name suggests... trickie's Avatar
    Join Date
    Jul 2002
    Location
    Melbourne, Australia
    Posts
    678
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    as a student developer it is pleasing to see the Eclipse library and be able to read and make sense of it. The PEAR library is alright, but so hard to read and learn from. ADODB is nice, but still it is harder to read the code to learn what they are doing.
    GO ECLIPSE!

  13. #13
    will code HTML for food Michel V's Avatar
    Join Date
    Sep 2000
    Location
    Corsica
    Posts
    552
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Vincent, how do I tell Eclipse's Database class to connect to MySQL or PostGre ?
    I didn't see any database-type setting, so I'm a bit confused
    [blogger: zengun] [blogware contributor: wordpress]

  14. #14
    SitePoint Addict richard_h's Avatar
    Join Date
    May 2002
    Location
    London
    Posts
    301
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    I would be interested in any comments regarding HarryF's original post (i.e. OO PHP App Design).

    My application design seems to change frequently, usually incorporating some new technique I've read about or has been recommended. My current approach involves using Smarty, my own DB class and few other classes I've written for commonly used tasks (validation, email, e.t.c).

    After reading a few threads on this forum and some interested articles on the Internet regarding OOP in PHP I can see my current working methods are floored in many ways.

    So opposed to me having a few classes for commonly untaken tasks, would I be right in thinking the way forward involves classes for everything? and I'm very interested in how people generate their presentation layer.

  15. #15
    SitePoint Evangelist
    Join Date
    Oct 2001
    Posts
    592
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Okay, some comments from me now.

    I think HarryF's design is mostly driven by what he wants his final results to be. I know this sounds vague, so I'll try to explain with an example: say that I'm building a web site that will have at every page a copyright-text in the footer. Following HarryF's style (I'm guessing here) this would result in a class Copyright that prints this text. There's nothing really wrong with this, but I think it could be more generic. Does class Copyright have to be a class on its own? Is it special and important enough to be given so many lines of code? I'd say: no. The copyright is nothing more than a special form of footer, so if there is going to be a special class presenting the copyright, I'd name the class Footer and not Copyright. That class will then support some way of setting the footer information, which will be - in this specific application - the copyright information. The difference between the two approaches may seem to be subtle, but I think it's pretty big. I wish I could explain it better. The word is 'abstraction'. Too little abstraction is a shame, because then you're only solving a special problem while you could be solving a general one. Too much abstraction is a problem as well, because it leads to incomprehensible code.

    In HarryF's code I see the classes ItemLogic, UpdateItem, GetItem and EditItem. Those names tell me nothing. What exactly are these classes supposed to do? In my terminology, class ItemLogic would probably be called 'Page'. Class 'ListItems' could be 'ItemListPage' and class 'GetItem' could be 'ItemViewPage'. It may seem silly I'm fussing about classnames, but a classname is very important. Not only should it mean something, it also drives your design. If you name a class 'DatabaseActions', you'll end up putting all sorts of database commands in a single class, which you probably don't really want to do. Having names like 'ItemViewPage' and 'ItemEditPage' suggests that the classes have something to do with each other, which is correct because they both handle items. At the same time it suggests that they have something to do with class 'Page', which is correct as well because they are subclasses of it.

    Class ItemContent is clearly the class that handles all database accesses. A few questions:
    - Why the name 'ItemContent'? Because you couldn't come up with a better, clear name? If a proper name can't be found for a class, it's existence is questionable.
    - The database connection is completely encapsulated in this class. Do you really want to do that, or do you want to be able to initialize the database connection from somewhere else, and then give it to the 'ItemContent' class for further handling? I would go for the latter.
    - Have the methods 'makeSelect' and 'makeChange' any real reason for being there?
    - All selection queries select each and every field from a table ('SELECT *'). Do you really need all fields all the time? Selecting all fields is often not a good idea; you'll only want to select the fields you're going to use. If the class isn't fit for doing that, then that should tell you something.
    - All insert- and updatequeries are MySQL specific. Quotes are used around non-string values and addslashes is used instead of str_replace. So why use database abstraction if you're not going to really use it anyway?
    - Inserts/Updates/Selects(/Deletes) are all in a single class. Why? Maybe inserts and updates have something to do with each other, but selects are very different. For updates you need safety and transactions; for selections all you need is a valid database connection.
    - The only proper way (IMHO) to handle whether magic quotes are disabled or not is to put a 'set_magic_quotes_runtime(0)' at the top of every script. This will ensure they are always disabled, which is what you really want them to be.

    Class ListItems is a subclass of class ItemLogic, and in it the method 'display' is defined. This method should also be defined in class ItemLogic, as it should be one that is overridden by subclasses. This ensures that the method 'display' always works for every subclass you're going to write. All in all, the inheritance used in the classes looks a bit strange to me, partly because of the classnames. I'm pretty sure classes EditItem and UpdateItem should be subclasses of class ItemLogic but they aren't right now. Also, their constructors are named wrong.

    After all these harsh words, can I still say something nice so thay HarryF won't start to detest me (if he didn't do that already...)? Of course I can. He uses classes to achieve two goals:
    1. Separating things that have little to do with each other.
    2. Grouping things that have much to do with each other.
    Both are very good goals, so in that sense HarryF's design is a good one. And just the fact that I would do it differently doesn't mean I would do it better. If there is such a thing.

    When I have a problem and am trying to solve it, I always aim for more than just the problem at hand. I don't try to solve the problem '6 + 3'; I try to solve the problem 'adding two numbers'. At every step I take towards the solution, I ask myself a lot of questions, like "Is this solution simple, efficient and understandable?" and "Can I apply this to more problems?" and "Are there any flaws in the design?" As soon as I come up with an answer I don't like, I start over. Every decision I make has a reason; a reason I can explain. If I take step for the wrong reasons (or no reasons at all), the step is most likely going to prove itself a bad one.

    In answer to richard_h:
    My application design seems to change frequently, usually incorporating some new technique I've read about or has been recommended
    There's nothing wrong with this, as long as using those new techniques improve the existing code. I am still Refactoring code I've written two or three years ago, but only to make the code better. Using a technique just because it exists, or just because it's new is not a good idea.

    Finally, in answer to Michel V:
    Vincent, how do I tell Eclipse's Database class to connect to MySQL or PostGre ? I didn't see any database-type setting, so I'm a bit confused
    You didn't see the database-type setting, because it doesn't exist To use MySQL you instantiate an object of class MyDatabase. For PostgreSQL you use PgDatabase. There is no abstract factory that instantiates the class you need. Why not? Because nine times out of ten you don't need it. Almost always a DBMS is chosen once for an application, and the application will stick with it. Do you really want use a lot of CPU power to parse a non-BNF connection string at every run of the application to find out which database class to use if you already know that in advance (like PEAR does)? I don't think so. I didn't implement the factory because it will be different for everyone. Some want a simple class that instantiates the right class given a simple string like 'mysql'. Others want lots more. Anyways, if you want a simple factory, here's one:

    PHP Code:
    class DatabaseFactory
    {
        function &
    createDatabase($type$name$host)
        {
            switch (
    strtolower($type))
            {
                case 
    'postgresql':
                case 
    'pgsql':
                case 
    'psql':
                case 
    'pg':
                    
    $class 'PgDatabase';
                    break;
                case 
    'mysql':
                case 
    'my':
                    
    $class 'MyDatabase';
                    break;
                case 
    'microsoft sql server':
                case 
    'ms-sql':
                case 
    'mssql':
                    
    $class 'MSDatabase';
                    break;
                case 
    'sybase':
                case 
    'sy':
                    
    $class 'SyDatabase';
                    break;
                default:
                    exit(
    "Invalid database type: $name");
            }
            include_once(
    ECLIPSE_ROOT $class '.php');
            return new 
    $class($name$host);
        }
    }

    $db =& DatabaseFactory::createDatabase('pgsql''dbname''localhost');
    if (!
    $db->connect())
    {
        exit(
    'Database connection failed!');

    Vincent

  16. #16
    As the name suggests... trickie's Avatar
    Join Date
    Jul 2002
    Location
    Melbourne, Australia
    Posts
    678
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    hey Vincent hope the graduation went well!

    I was wondering what your (or anyone's) thoughts were on Question 1 from Harry's original post...

    I always run into this problem. I think i have a structured out a good architecture, but it's the initial parsing and distributing of the request (Harry calls it a proxy) that get's me.

    I recently have been trying to work out a way to do this distribution in a REST based architecture where the data for the app is distributed into distinct URI's (Harry also got me into REST)

    any way got any thoughts?

  17. #17
    SitePoint Evangelist
    Join Date
    Oct 2001
    Posts
    592
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    hey Vincent hope the graduation went well!
    Yeah, it went pretty well. Thanks for asking!

    I was wondering what your (or anyone's) thoughts were on Question 1 from Harry's original post...
    If there is only one real page on the site ('index.php'), there has to be some if/switch or whatever to drive the application. Somehow, the right code has to be executed.

    If-statements (or switch-statements, which are basically if-statements) have a problem: at most 50% of the code in them is executed. As more and more cases are added, the percentage drops. For compiled code that's not so bad, but for PHP it is, because the interpreter must parse and read the whole code, even though it skips most of it. For programmers, large switches are bad as well. If there are too many cases, or if a case contains too much code, the whole statement becomes hard to understand.

    My normal approach is to 'mask' the statement with a factory, which is possible if all cases use the same type of code (object instantiation). For example (no error-checking done, mind):

    PHP Code:
    $class getClassForEvent($_GET['event']);
    include_once(
    $class '.php');
    $itemApp =& new $class($arguments); 
    In this case the function getClassForEvent translates an event name in the GET array to an actual class name. This can be a simple switch statement, or maybe a database lookup, or whatever floats your boat.

    However, it only works because each and every class that can be instantiated expects the same set of arguments ('$arguments' in the example). In HarryF's code this is not the case:

    PHP Code:
        case "formResponse"
            
    $itemApp = new UpdateItem($_POST,$_GET['item_id']); 
            break; 
        case 
    "updateForm"
            
    $itemApp = new EditForm$articleData,$_GET['item_id'] ); 
            break; 
        case 
    "insertForm"
            
    $itemApp = new EditForm$articleData ); 
            break; 
        case 
    "getItem"
            
    $itemApp = new GetItem$itemContent ); 
            break; 
        default: 
            
    $itemApp = new ListItems$itemContent ); 
            break; 

    Sometimes he needs $_POST and $_GET, at other times he needs $articleData, ...

    There are two possible solutions to this problem:
    1. Implement some way to get the right set of arguments given an event-name (like getClassForEvent but for arguments). This tends to lead to ugly code.
    2. Make sure all classes expect the same set of arguments. This is the cleanest approach, but also has the most consequences.

    The first case isn't very interesting, so I'll concentrate on the second.

    When all classes must expect the same set of arguments, the logical first step is to create a base class that expects that specific set. But what does this set look like? In HarryF's code that's not so clear. It could be all possible variations of the arguments passed to the various objects. But that is ugly, as you're then passing arguments to objects that don't need them. So a subset would be better. But which set? And how would the subclasses get to the additional arguments they need?

    All of the problems can be solved at once by putting the switch-statement (or factory) in a class of its own, like this:

    PHP Code:
    class Application
    {
        function 
    run()
        {
            
    $class $this->getClassForEvent($_GET['event']);
            include_once(
    $class '.php');
            
    $page =& new $class($this);
            
    $page->show();
        }

    As you can see, instead of passing a set of arguments, I pass (a reference to) the object that creates the object. So the set of classes could look like this:

    PHP Code:
    // Base class
    class Page
    {
        var 
    $application;

        function 
    Page(&$application)
        {
            
    $this->application =& $application;
        }
    }

    class 
    DatabaseDrivenPage
    {
        var 
    $database;

        function 
    DatabaseDrivenPage(&$application)
        {
            
    $this->Page($application);
            
    $this->database =& $application->getDatabase();
        }

    So instead of passing a set of arguments to an object on creation; those objects themselves are responsible for getting the arguments they need by calling various access-methods on the object that created it ($application->getDatabase()). Of course it's not strictly one or the other.

    Class Application is also suitable for doing all kinds of other initialization stuff, like setting up the database connection, reading configuration files and including HTML templates. As a result, 'index.php' becomes just this:

    PHP Code:
    require_once('Application.php');
    $app =& new Application();
    $app->run(); 
    ...which looks just fine by me

    Vincent

  18. #18
    As the name suggests... trickie's Avatar
    Join Date
    Jul 2002
    Location
    Melbourne, Australia
    Posts
    678
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    thanks...

    i needed a better understanding and example of web app initialisation.

    I've done a fair bit of playing around with PHP, but most of my formal software engineering work has been done in Java, C and the development of standalone apps.

    I think i just convinced myself that web apps were a different kettle of fish, but the same rules of architecture can be applied to them as standalones

    any way thanks, i have a clearer understanding now


  19. #19
    SitePoint Wizard gold trophysilver trophy
    Join Date
    Nov 2000
    Location
    Switzerland
    Posts
    2,479
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Awesome feedback! Thanks Vincent! No problems on criticism - I personally learn best from other people and the only way to do that is to be prepared to be a jacka$$

    Just dropping by quickly this evening (had too much to do recently) but gonna take a long look at my code tomorrow (nodding with all you're saying and the particularily taken with getClassForEvent).

    Many thanks! And glad the defence went well.

  20. #20
    SitePoint Addict richard_h's Avatar
    Join Date
    May 2002
    Location
    London
    Posts
    301
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    In ItemLogic.php, I'm including PHP files which are HTML mixed with a little PHP. Is this the right place to be doing this? What alternative approaches are there to templating for displaying the user interface and how roughly can they be implemented in this design?
    Are there any takers on this one?

    I've used various template classes in the past (FastTemplate, Smarty etc) is this the right approach for a truly OO PHP application? and going back to HarryF's comments what are the alternatives?

    Thanks in advance.

  21. #21
    SitePoint Wizard gold trophysilver trophy
    Join Date
    Nov 2000
    Location
    Switzerland
    Posts
    2,479
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Been pondering that some more, inspired by XCS (an XML-RPC "class server" - a truly a clever piece of coding).

    Here's what I'm starting to think. Your classes / code are divided into two groups;

    1. Base / utility classes, like Vincents Eclipse library - these are ones for jobs you need to repeat all over your application - everything from connecting to databases to user interface generating classes (in ASP.NET terms: form controls).

    2. Application classes / code. These tie the utility classes together into a specific application.

    The base classes are "out of sight" - in a secure directory available only to the PHP scripts themselves.

    The application classes are placed in directories below the web root, depending on their purpose. These should perhaps also be protected from prying eyes.

    Then a single PHP front end acts as a "proxy" to all the application classes.

    So for example, using what we started with here, you have;

    ./index.php << in the web root
    ./article/class.article.php << a base class for articles
    ./article/show/class.showarticle.php << shows an article
    ./article/list/class.listarticles.php << lists all articles

    Now someone accessing your site uses the URL;

    http://www.domain.com/index.php/article/list/

    And gets a list of articles.

    index.php includes / instantiates the correct class depending on the URL it's given. Using some cunning .htaccess files or virtual hosts, this could be reduced to http://www.domain.com/article/list/ and still be "proxied" through index.php

    Viewing a single article might be like;

    http://www.domain.com/index.php/article/view/23/

    But this is nothing new - just re-invented what ezPublish does.

    That kind of answers richards question - with all the baes classes providing form controls, tables etc., no need for templates.

  22. #22
    SitePoint Addict richard_h's Avatar
    Join Date
    May 2002
    Location
    London
    Posts
    301
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Thanks for the feedback Harry and Vincent, some very interesting issues discussed in this thread.

  23. #23
    SitePoint Enthusiast
    Join Date
    Sep 2002
    Location
    Canada
    Posts
    27
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    I've read through this thread and others over the past few days and have been trying to wrap my brain around OOP. One thing I don't understand is code like this:

    PHP Code:
    class Jar {
       function 
    openJar() {
          ...
       }
    }

    Jar::openJar(); 
    Instead of creating the openJar() functionality as a method inside class Jar, wouldn't it be simpler to just create it as a function and call the function normally?

    Thanks everyone, and (joining the bandwagon here) especially Vincent, over the past few days the way I approach programming has been completely changed, largely because of you.

  24. #24
    SitePoint Enthusiast aivarannamaa's Avatar
    Join Date
    Sep 2002
    Location
    Estonia
    Posts
    36
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    I try to answer ThreeDee question --

    openJar is class method, ie. not dealing with any particular object, so it's really very much like oardinary function. I think class methods are used for keeping related items together. If class has only class methods, then its like a function library, ie. not quite a "normal" class.

    If "normal" class has a class method, then maybe some of the object methods uses it. Class method can be overriden (unlike ordinary function), so you can provide different versions for subclasses if they need it.

    Aivar

  25. #25
    SitePoint Enthusiast aivarannamaa's Avatar
    Join Date
    Sep 2002
    Location
    Estonia
    Posts
    36
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    - The database connection is completely encapsulated in this class. Do you really want to do that, or do you want to be able to initialize the database connection from somewhere else, and then give it to the 'ItemContent' class for further handling? I would go for the latter.
    Then where to make database connection?

    I have a controller and model class. Model needs to use database and it needs to know about current user (allow something for some users and disallow for others). If I make db connection in controller (using for example basic auth usename and password) and hand it to model then I need to pass username and password also, if I leave connecting to model, then I have to pass only username and password. And controller shouldn't care, whether model needs a database access at all or not. So it seems to me that making database connection in the model is better.

    Heh, writing this cleared the things a bit for me, but still, I'd be happy if anyone commented this issue.

    Another thing is relationship between model and database accessing code. HarryF has separated these into two classes and I've seen it elsewhere too. I understand the need for database abstraction layer but for me business logic and sql seem to very be tightly related. I fact, usually sql contains all the business logic I have. What do I gain when I separate sql from business logic?


    Third. Vincent, you said, it's wrong to have selects, updates and inserts together in one class. I still don't understand why. Can you please explain it further?

    Thank you!

    Aivar


Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •