Take the 2-minute tour ×
Programmers Stack Exchange is a question and answer site for professional programmers interested in conceptual questions about software development. It's 100% free, no registration required.

I've been trying now for a long time to wrap my head around OOP. I see its advantages. I've read many, many tutorials and watched an equal amount of videos on the subject. I get the animal/cat/dog examples, I get the car/drive examples. What I'm struggling with is how to apply these concepts in a real-world application. So, I've set out to build one using OOP.

I am not asking for help with syntax or writing specific code — I can find that myself in documentation and by searching forums etc.. What I really need is some guidance, and a push in the right direction every now and then. Are there any seasoned programmers willing to mentor me?

As my learning project I would like to build a simple classifieds "web app". Something similar to Craigslist but way watered down in terms of scope. I would like to use PHP5 and MySQL, because I am familiar with them.

Let's say there are only these 2 use cases:

  1. Posting something for sale
  2. Browsing/searching for something to buy

What "things" should be objects? I can imagine that each item could be an object, but at what point? And why?

So for example, the user fills out the "post item for sale" form, should that form get turned into an object which gets passed to another object which inserts the values into a database?

What about when another user is browsing and requests to see all items of category C? Does it make sense that whenever the app has to connect to its database it creates a database object and then gets a bunch of item objects and displays them on the page? …writing this out certainly makes me realize how clueless I still am about OOP. Please help me fix that.

If in your opinion this is not a good project to start wading into OOP with, please feel free to suggest another idea!

share|improve this question
3  
+1 for the animal/cat/dog examples –  MrCode May 16 '12 at 13:20
1  
I'm in the same boat, I think I understand OOP - been a while since I attempted Java, but when it comes to PHP I would know how to do things like this instantly the 'normal' way but when it comes to thinking how it would be done using OOP I lose the will to live. –  martincarlin87 May 16 '12 at 13:20
    
The form is not turned into an object. An object is an instance of a class. You could see it like this. $item->saveItem($_POST['name'], $_POST['description']); edit What really helped me figure out OOP is creating a simple "guestbook" web-app. Make users log in, post messages, edit messages, delete messages and search for messages etc. –  Bono May 16 '12 at 13:22
    
@pduersteler good idea, how do I go about doing that? Admittedly, this is my first question on stackoverflow :) –  Bern May 16 '12 at 13:24
    
@Bono maybe a guestbook app like you mentioned is indeed a better place to start. The other one I was thinking about was a very simple list app where users login, make/edit/delete lists, add/edit/delete items in those lists. Would you mind sharing your guestbook app with us/me? –  Bern May 16 '12 at 13:29
show 10 more comments

migrated from stackoverflow.com May 16 '12 at 13:48

This question came from our site for professional and enthusiast programmers.

6 Answers

I honestly think that the advice here has been terrible for new OO learners so far. It's not a good idea to immediately start thinking of objects as representations of a specific instance of a "thing" defined by some class. It's better to think of them as compartmentalized components of a machine that have some interaction with one another, but not each others' internals. Each of these components maintains state

If you want to use an ORM (object-relational-mapping) for DB interactions, whatever framework you use or create will probably have some shallow objects representing tables, which are probably collections of "things," but I don't like ORMs personally, and I don't think they necessarily represent ideal OO practices, but they are popular for large web-apps.

Besides that, you will probably have some important components that the web-app machine needs to run such as one or more DB connections (you can create a class that maintains a connection and you can run prepared queries from -- PDO is great on its own, but I would wrap it), and perhaps a template system for your views. You may want your controllers to be PHP objects as well. If you have a form to fill out, you may have an object that maintains form values for P/R/G, a CSRF-protection token, and can perform validation on its inputs.

You shouldn't try to look for "things" to turn into objects when constructing your web-app design and object graph. Instead, you should think about the logical components that come together to create it. I don't think you should try to force this, and it should come fairly naturally, but it's very hard to do correctly and you will definitely end up having to change some design decisions along the way.

My final advice is this: composition over inheritance is the way to go.

share|improve this answer
3  
+1 for composition vs inheritance –  dj18 May 16 '12 at 17:18
    
A rule of thumb I have, specially for dynamic languages, is to try to only create classes if I want to take advantage of polymorphism (that is, if those classes will implement different versions of the same method, and the logic will depend on that somehow). Otherwise, I try to err on writing in a more "procedural" style, in order to keep it simple. –  missingno May 17 '12 at 14:59
add comment

Here is how you can use OOP to buy and sell your pets, the same methodology could be used to sell cars or planes ;p

<?php
// define a superclass .. no instances will be made of 'animal' itself,
// but it is useful to define common characteristics and behaviours
// (ie: properties and methods) of all our classes of animals
class Animal {

    // this constructor function is called whenever a new instance
    // of the Animal class is created (or any class that inherits from Animal)
    function Animal ($colour) {

        // install the argument as an attribute of any instances of Animal
        $this->colour = $colour;
    }

    // this method will be available to all classes that inherit from Animal
    function report () {
        return "This ".$this->colour." ".get_class($this)." has ".$this->legs." legs.<br />";
    }
}

// this class inherits from Animal
class Cat extends Animal {

    // set the legs attribute
    public $legs = 4;

    // create a method that can be called from any instances of Cat
    function make_noise () {
        echo "MEOW!<br />";
    }
}

// this class inherits from Cat, and from Animal
class Panther extends Cat {

    // specifies the colour attribute
    public $colour = "black";

    // overwrites the constructor function that would otherwise be
    // inherited from Animal, with a blank constructor.
    function Panther () {}

    // overwrites the method inherited from Cat
    function make_noise () {
        echo "ROARRRR!<br />";
    }
}

// this class inherits from Animal
class Snake extends Animal {
    public $legs = 0;
}

// this class is unrelated to the others
class PetShop {

    // set up an array to store the pets that the shop will stock
    public $pets = array ();

    // set up a variable to store the total cash in the pet shop
    public $cash;

    // this method creates a new object and adds it to the pets array
    function add_pet ($petclass, $price, $colour) {

        // set up a variable containing the number of elements in the pets array
        $n_pets = count($this->pets);

        // add to the pets array, a new instance of the class specified as
        // the first argument in this method, using the last argument as the
        // colour argument that is passed to the specified class's constructor
        $this->pets[$n_pets] = new $petclass($colour);

        // add a 'price' attribute to the pet object
        $this->pets[$n_pets]->price = $price;
    }

    // this method removes the specified pet from the array and adds the price
    // to the pet shop's cash variable
    function sell_pet ($n) {

        // add pet's price to the cash total
        $this->cash += $this->pets[$n]->price;

        // remove the pet object from the array
        array_splice($this->pets, $n, 1);

        // give a message about the sale
        echo "SALE: Pet no. ".$n." sold. Total cash is now \$".$this->cash.".<br /><br />";
    }

    // this method reports on the pet shop's stock
    function show_pets () {

        // show the number of pets available
        echo "<B>Shop stock:</B><br />We have ".count($this->pets)." pets for sale.";
        echo "<br /><br />";

        // iterate through the pets array and show information about each one
        for ($i = 0; $i < count($this->pets); $i++) {
            echo "<B>Pet No. ".$i.": </b>".$this->pets[$i]->report();
            echo "Price: \$".$this->pets[$i]->price."<br />";
        }
        echo "<br />";
    }
}

// instantiate a new PetShop object
$shop = new PetShop ();

// add three pets to the shop
$shop->add_pet(cat, 20, "tabby");
$shop->add_pet(snake, 40, "brown");
$shop->add_pet(snake, 60, "black");

// show the pet's stock
$shop->show_pets();

// sell the first pet in the stock
$shop->sell_pet(0);

// show the pet's stock after the sale
$shop->show_pets();
?>
share|improve this answer
27  
If I see one more oop example with cars or animals, I am gonna lose it –  bye May 16 '12 at 17:05
add comment

On request of OP I'll share my guestbook code.
Message class:

<?php 
Class message
{
    private $db;
    private $messageID;
    private $message;
    private $name;
    private $mail;

    public function setmessageID($messageID)
    {
        $this->messageID = $messageID;
    }

    public function getmessageID()
    {
        return $this->messageID;
    }

    public function setmessage($message)
    {
        $this->message = $message;
    }

    public function getmessage()
    {
        return $this->message;
    }

    public function setname($name)
    {
        $this->name = $name;
    }

    public function getname()
    {
        return $this->name;
    }

    public function setMail($mail)
    {
        $this->mail = $mail;
    }

    public function getMail()
    {
        return $this->mail;
    }
}

Message Data Access Object Class:

<?php 
class messageDAO
{
    private $db;
    private $aantalMessages;
    private $messages;
    private $message;

    //bij laden roept hij automatisch Db class aan (en de daarbij gezeten functies)
    public function __construct(Db $db)
    {
        $this->db = $db;
    }

    public function getMessages()
    {
        return $this->messages;
    }

    public function getAantalMessages()
    {
        return $this->aantalMessages;
    }

    //Function to retrieve messages
    public function findMessages($args)
    {       
        $dbh = $this->db->DBH();

        //$offset for pagination
        $offset = ($args['currentPage'] - 1) * $args['itemsPerPage'];

        $sth = $dbh->prepare("SELECT    SQL_CALC_FOUND_ROWS
                                                    messageen.messageID, 
                                                    messageen.message, 
                                                    messageen.name, 
                                                    messageen.mail
                                            FROM    `messageen` 
                                            ORDER BY messageen.datumToegevoegd DESC 
                                            LIMIT   ?, ?");
        $sth->bindParam(1, $offset, PDO::PARAM_INT);
        $sth->bindParam(2, $args['itemsPerPage'], PDO::PARAM_INT);
        $sth->execute();
        $sth->setFetchMode(PDO::FETCH_ASSOC);

        $messages = array();

        while($row = $sth->fetch())
        {
            $message = new message();
            $message->setMessageID(htmlentities(strip_tags($row['messageID'])));
            $message->setSessage(htmlentities(strip_tags($row['message'])));
            $message->setName(htmlentities(strip_tags($row['name'])));
            $message->setMail(htmlentities(strip_tags($row['mail'])));  
            $messages[] = $message; 
        }

        $sth = $dbh->prepare("SELECT FOUND_ROWS() as numberOfMessages");
        $sth->execute();
        $sth->setFetchMode(PDO::FETCH_ASSOC);
        $this->numberOfMessages = $sth->fetch();

        return $messages;
    }

    public function setMessageToEdit($args)
    {   
        $sth = $this->db->DBH()->prepare("SELECT    messages.message
                                            FROM    `messages`
                                            WHERE   messages.messageID = ?");
        $sth->bindParam(1, $args['messageID']);
        $sth->execute();
        $sth->setFetchMode(PDO::FETCH_ASSOC);
        //return the retrieved message
        while($row = $sth->fetch())
        {
            $message = new message();
            $message->setMessage(htmlentities(strip_tags($row['message'])));
            $message->setMessageID(intval($args['messageID']));
        }

        return $message;
    }

    //functie om messageen aan te passen
    public function save(message $message)
    {   
        //insert part
        //if(isset($message->getname()) && isset($message->getmessage()) && isset($message->getMail()))
        //{
            $sth = $this->db->DBH()->prepare("INSERT INTO   `messages`
                                                    SET     messages.name = ?,
                                                            messages.mail = ?,
                                                            messages.message = ?,
                                                            messages.dateAdded = NOW()");
            $sth->bindParam(1, $message->getName());
            $sth->bindParam(2, $message->getMail());
            $sth->bindParam(3, $message->getMessage());
            $sth->execute();
        //}

        //update part       
        /*if(isset($message->getmessageID()) && isset($message->getmessage()))
        {
            $sth = $this->db->DBH()->prepare("UPDATE    `messageen`
                                                SET     messageen.message = ? 
                                                WHERE   messageen.messageID = ?
                                                LIMIT   1");
            $sth->bindParam(1, $message->getmessage());
            $sth->bindParam(2, $message->getmessageID());
            $sth->execute();
        }*/
    }
}

index.php

<?php
//include file loader.php
include("includes/loader.php");

$guestbook = new guestbook($db);
$user = new user($db);
$messageDAO = new messageDAO($db);

//Make a array named error
$error = array();

//Get action (login/setmessage/editmessage/deletemessage)
if(isset($_GET['action']))
{   
    switch ($_GET['action'])
    {   
        //if login submit is pressed
        case 'login':
            //Check if filled
            if(isset($_POST['username']) && isset($_POST['username']))
            {
                $error['usernameEmpty'] = (bool) !strlen(trim($_POST['username']));
                $error['passwordEmpty'] = (bool) !strlen(trim($_POST['password']));
            }

            if(in_array(1, $error))
            {
                //Assign $error to smarty
                $smarty->assign('error', $error);
            }

            else
            {
                if(isset($_POST['username']) && isset($_POST['username']))
                {
                    $user->setLoggedIn(array('username'=>$_POST['username'],
                    'password'=>$_POST['password']));

                    if($user->getLoggedIn() != true)
                    {                   
                        $smarty->assign('loggedInError', $user->getLoggedIn());
                    }
                }
            }
            break;

        //Als if "place message" is pressed
        case 'placemessage':
            //if user is not logged in
            if($user->getLoggedIn() != true)
            {
                //Controleren of message-velden wel zijn ingevuld
                $error['nameEmpty'] = (bool) !strlen(trim(htmlentities(strip_tags($_POST['messagename']))));
                $error['mailEmpty'] = (bool) !strlen(trim(htmlentities(strip_tags($_POST['messageMail']))));
                $error['messageEmpty'] = (bool) !strlen(trim(htmlentities(strip_tags(str_replace('place message...','', $_POST['messageInput'])))));

                if($error['mailEmpty'] != 1)
                {
                    $error['mailInvalid'] = !filter_input((INPUT_POST), 'messageMail', FILTER_VALIDATE_EMAIL);
                }

                if(in_array(1, $error))
                {
                    $smarty->assign('error', $error);
                }

                else
                {
                    $message = new message();

                    $message->setname($_POST['messagename']);
                    $message->setMail($_POST['messageMail']);
                    $message->setmessage($_POST['messageInput']);

                    dump($message);

                    //place message             
                    $messageDAO->save($message);
                }
            }

            //if user is logged in
            else 
            {
                //is message filled?
                $error['messageEmpty'] = (bool) !strlen(trim(htmlentities(strip_tags(str_replace('place hier uw message...','', $_POST['messageInput'])))));

                if($error['messageEmpty'] != 1)
                {   
                    $user->setUser();

                    $guestbook->placemessage(array('name'=>$user->getLoggedInUsername(), 
                    'mail'=>$user->getLoggedInUserMail(),
                    'messageInput'=>$_POST['messageInput']));
                }

                else 
                {
                    $smarty->assign('error', $error);
                }
            }
            break;

        case 'deletemessage':
            $user->setUser();

            if($user->getLoggedInUserAdmin() == 1)
            {
                if(isset($_GET['messageID']) && is_numeric($_GET['messageID']) && isset($_GET['key']))
                {
                    $guestbook->setURLKey($_GET['messageID']);

                    if($guestbook->getURLKey() == $_GET['key'])
                    {                   
                        $guestbook->verwijdermessage(array('messageID'=>$_GET['messageID']));
                    }
                }
            }
            die(header("location: /index.php"));
            break;
    }
}

if(isset($_GET['pagina']) && is_numeric($_GET['pagina']))
{

    $currentpage = $_GET['pagina'];
}

else
{
    //$currentpage is 1
    $currentpage = 1;
}

$user->setUser();

//assign var to smarty
$smarty->assign('messages', $messageDAO->findmessages(array('currentpage'=>$currentpage, 'itemsPerPagina'=>10)));
$smarty->assign('user', $user);

//Pagination

$numbermessages = $messageDAO->getnumbermessages();


$totalpages = ceil($numbermessages['numbermessages'] / 10);


if($currentpage < 1)
{
    //$currentpage is 1
    $currentpage = 1;
}


if($currentpage > $totalpages)
{

    $currentpage = $totalpages;
}

$smarty->assign('numbermessages', $messageDAO->getnumbermessages());
$smarty->assign('guestbook', $guestbook);
$smarty->assign('currentpage', $currentpage);
$smarty->assign('totalpages', $totalpages);

//display index.tpl
$smarty->display('index.tpl');

I renamed some of the variables and function to make sense to you (translated from dutch to english :P ) so you might find some weird sentances sometimes because i just did a quick replace etc. Have fun with it. Also this isnt the whole code because that would result in me posting like 20 files worth of code :P

share|improve this answer
add comment

As mentioned by Explosion Pills, in a complex application most of the objects are relating to application components (e.g. database connection pools, commands, data structures such as hashmaps) rather than real world entities (such as a boarding pass, invoice, or mp3 file). There are many good books on design patterns that show you ways that people have solved a lot of recurring problems in this area. The GOF book as it is known is thorough but very dry, Head First Design Patterns may be more accessible.

In terms of real world analysis and design. It is often helpful to think in terms of nouns and verbs. For example a video lending library (are these obsolete now?) may have these things/nouns:

  • Video
  • Borrower

In terms of verbs:

  • A Borrower can take out a video for a length of time
  • A Borrower can return a video to the store etc.

These can then be turned into classes with operations (it's a long time since I've done any PHP so I'll avoid it):

class Borrower
{
  public void borrow(Video video, int daysToBorrow)
  {
     ...
  }

  public void returnVideo(Video video, boolean calculateFine)
  {
     ...
  }
}

It all takes a LOT of practice and playing around. The best thing to do is get stuck in and learn from failed designs. In my opinion OO is something that you can continue to learn and develop over your lifetime (it is not easy and there are no perfect solutions to anything). Good design is often iterative, so expect to try out a few different ideas for your "Craig's List" webapp.

share|improve this answer
add comment

The best thing to do is to find a way of concentrating on the core of your application - "post", "user", "post::FindByName()", "user->Validate()" etc., and not worry too much about the plumbing - how to glue posts to database tables, how to keep the display for a post consistent between different searches, and how to glue the "enter post" form to a database record.

Luckily, there are lots of frameworks that do this for you; the dominant paradigm in OO web applications is "Model-View-Controller", also known as MVC; in PHP, there are a number of off-the-shelf MVC frameworks you can use.

Whilst this expands your need to learn - you now have to learn about MVC as well as OO - it means that your OO efforts are mostly constrained to the "Model" layer, which represents your business domain; that's where OO is most natural and expressive. Most of the MVC frameworks allow you to define your "model" layer, and then automagically create a web site around that using a technique known as scaffolding - that way, you get a quick way of experimenting with different implementations for your domain model, without having to unpick all the plumbing.

share|improve this answer
add comment

Although I haven't had the chance to go through it yet, I found a Lynda.com course entitled PHP with MySQL Beyond the Basics which promises to cover exactly what I was looking for. Unfortunately I can't comment on the quality of what is presented in the course yet, but I'll come back to do so after having completed it.

http://www.lynda.com/PHP-tutorials/php-with-OOP-beyond-the-basics/653-2.html

UPDATE:

Lynda.com JUST released a course entitled Foundations of Programming: Object-Oriented Design. EXACTLY what I was looking for. Gonna give it a watch.

http://www.lynda.com/Java-tutorials/Foundations-Programming-Object-Oriented-Design/96949-2.html

UPDATE:

So far I'm finding the Foundations of Programming: Object-Oriented Design course excellent.

share|improve this answer
    
…not a good idea? –  bernk May 20 '12 at 16:05
    
Glad you found a good example ;) –  Bono Aug 3 '12 at 17:03
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.