OOP clarification

Collapse
X
 
  • Time
  • Show
Clear All
new posts

  • Dormilich
    replied
    just another note on Exceptions:

    the Exception::getT race() and Exception::getT raceAsString() methods can be very valuable in the debugging process. (these values should not be visible to any visitors, because they reveal quite some info about your script).

    Leave a comment:


  • Atli
    replied
    To answer a couple of your questions...

    Originally posted by fjm
    Ok, is this what you mean by change privately declared member:
    In a way, yes.
    What I mean is that you should be able to completely change the interior of the class (the private members and methods) without the outside code ever knowing about it.

    Using properties (setters and getters) can go a long way towards that.
    Originally posted by fjm
    Could you elaborate on this a bit? What is a parameter list? Are these the values passed into the constructor?
    Yea, that's exactly what I mean.
    If you specify a default value for one of these parameters it is considered optional.
    (See the constructor and moveFile method in the code in my previous post)
    Originally posted by fjm
    I kind of felt that was incorrect. I actually made like a trigger method as you had done in your example but was having a problem getting the methods to work correctly. method C was dependant on B and B was dependant on A. When I just listed them like you showed, All of the methods would execute. In order for me to get them to work like that, I had to nest them based on their return values. Was this right?
    If you are going to use return values for error handling, then yes, that is how you should do it.
    If the success or failure of function A affects whether function B should be executed, you will need to capture it's return value and check it before executing function B, just as you probably did to begin with :)

    Exceptions give you an alternative to this, so you don't neccissarilly have to check the return values, but rather try to catch the exception.
    It can make the code easier to manage, especially if you are nesting function calls very deeply.
    Originally posted by fjm
    When you say "initialize the object" what exactly does that mean? What if there are no params required for the constructor? Do you mean that I should initialze all my class members in the constructor?
    That is the general idea, yes.
    PHP is a loosely typed language, so initializing members isn't as vital as in other languages (C/C++ and such), but you should at least always keep it in mind when writing the constructor.
    Originally posted by fjm
    Of course, if I did it this way, I'd have to make these methods public, right?
    Not really, no.
    Private methods can return values just as public once can. They just can't be called by anything outside the class.

    The return values can still be very useful within the class itself.

    Leave a comment:


  • Atli
    replied
    Ok, this is a bit different from your class, but should at help explain what I'm talking about.

    This is how I would have written a class like the one you posted.
    (Been meaning to make a proper uploader class for ages anyways :P)

    It is made so that you can move multiple files with one instance, so I removed most of the private members you used and added getter and setters for the once I left and added.

    I also inlined most of the private methods into a public 'moveFile' method and a private 'validateFile' method, which do all the work.
    The constructor just initializes the members and makes sure everything is ready.

    I'm not sure how much you've used exceptions, but I use them in this class for error handling. Hope that doesn't get in your way.

    I may have "over-commented" it a bit, but I always do that :P
    (See an example of how to use it at the bottom)
    [code=php]<?php
    /**
    * Handles file uploads from a HTTP POST request.
    * @throws Exception
    */
    class FileUpload
    {
    /** Property values **/
    protected $_uploadDir; // Where to save the files
    protected $_maxFileSize; // In bytes
    protected $_allowedTypes; // A list of allowed mime types and their file extensions.

    /**
    * The constructor. All parameters are optional.
    * @param [string] $uploadDir The directory where files should be moved.
    * @param [integer] $maxFileSize The maximum size of a uploaded file in bytes.
    * @throws Exception
    */
    public function __construct($up loadDir="", $maxFileSize=20 97152)
    {
    // Verify that there were some files uploaded
    if(!$_FILES || count($_FILES) <= 0) {
    throw new Exception("No files were uploaded.");
    }

    // Initialize the properties
    $this->setUploadDir($ uploadDir);
    $this->_maxFileSize = $maxFileSize;
    $this->_allowedType s = array(
    'image/jpeg' => array('jpeg', 'jpg'),
    'image/jpg' => array('jpeg', 'jpg'),
    'image/png' => array('png'),
    'image/gif' => array('gif')
    );
    }

    /**
    * Moves a file from it's temporary upload location to the upload dir.
    * @param string $elemName The name given to the <input> element of the form
    * @param [string] $newName A new name for the file, excluding the extension.
    * @throws Exception
    */
    public function moveFile($elemN ame, $newName=null)
    {
    try {
    // Make sure the file is valid
    $this->validateFile($ elemName);

    // Parse the file name and extension
    $baseName = basename($_FILE S[$elemName]['name']);
    $fileName = substr($baseNam e, 0, strripos($baseN ame, "."));
    $fileExt = substr($baseNam e, strripos($baseN ame, ".") + 1);

    // Set and clean the name
    if($newName != null) {
    $fileName = $newName;
    }
    $fileName = $this->cleanFileName( $fileName);

    // Move the file
    $newPath = $this->_uploadDir . $fileName . $fileExt;
    if(!move_upload ed_file($_FILES[$elemName]['tmp_name'], $newPath)) {
    throw new Exception("Coul d not save file '{$newPath}'");
    }
    return true;
    }
    catch(Exception $ex) {
    throw new Exception("File relocation failed: " . $ex->getMessage() );
    }
    }

    /**
    * Converts a filename into a 'clean' format.
    * @param string $fileName The filename to clean
    * @return string The result of the cleaning
    */
    private function cleanFileName($ fileName)
    {
    $temp = $fileName;
    $temp = strtolower($tem p);
    $temp = str_replace(' ', '_', $temp);
    $result = '';
    for ($i=0; $i<strlen($temp ); $i++)
    {
    if (preg_match("([0-9]|[a-z]|_|.)", $temp[$i]))
    {
    $result = $result . $temp[$i];
    }
    }
    return $result;
    }

    /**
    * Makes sure a uploaded file is valid and ready to be moved.
    * @param string $elemName The name given to the <input> element of the form
    * @return bool
    * @throws Exception
    */
    private function validateFile($e lemName)
    {
    // Make sure the element exists
    if(!isset($_FIL ES[$elemName])) {
    throw new Exception("No upload named '{$elemName}' was found");
    }

    // Make sure the file was uploaded without a error
    if($_FILES[$elemName]['error'] != 0) {
    throw new Exception("File upload failed with code #". $_FILES[$elemName]['error']);
    }

    // Make sure the file extension and mime type are valid
    $fileExt = substr($_FILES[$elemName]['name'], strripos($_FILE S[$elemName]['name'], ".") + 1);
    $fileMime = $_FILES[$elemName]['type'];

    if(!in_array($f ileMime, array_keys($thi s->_allowedTypes) ) ||
    !in_array($file Ext, $this->_allowedType s[$fileMime]))
    {
    throw new Exception("File mime or extension is not valid.");
    }

    // Make sure the file size is correct and within limits
    if($_FILES[$elemName]['size'] != filesize($_FILE S[$elemName]['tmp_name'])) {
    throw new Exception("Uplo aded size and actual size do not match");
    }
    if($_FILES[$elemName]['size'] > $this->_maxFileSize ) {
    throw new Exception("File size is to big");
    }
    return true;
    }

    /** UploadDir and MaxFileSize properties (getters and setters) **/
    public function setUploadDir($n ewPath) {
    // Format the path
    $newPath = str_replace('\\ \\', "/", $newPath);
    if($newPath[strlen($newPath )-1] != "/") {
    $newPath .= "/";
    }

    // Make sure the path is accessible
    if(file_exists( $newPath) && is_writeable($n ewPath)) {
    $this->_uploadDir = $newPath;
    }
    else {
    throw new Exception('Uplo ad directory is inaccessible.') ;
    }
    }
    public function getUploadDir() {
    return $this->_uploadDir;
    }
    public function setMaxFileSize( $newValue) {
    if(is_integer($ newValue) && $newValue > 0) {
    $this->_maxFileSize = $newValue;
    }
    else {
    throw new Exception("The max file size must be a positive integer.");
    }
    }
    public function getMaxFileSize( ) {
    return $this->_maxFileSize ;
    }

    /** Getters and setters to add / retrive allowed mime types and extensions */
    public function addAllowedMimeT ype($mime, $extensions) {
    foreach($extens ions as $_ext) {
    $_ext = strtolower($_ex t);
    if(!array_key_e xists($mime, $this->_allowedType s) ||
    !array_search($ _ext, $this->_allowedType s[$mime]))
    {
    $this->_allowedType s[$mime][] = $_ext;
    }
    }
    }
    public function getAllowedMimeT ypes() {
    return array_keys($thi s->_allowedTypes) ;
    }
    public function getAllowedMimeE xtensions($mime Type) {
    if(array_key_ex ists($mimeType, $this->_allowedTypes) ) {
    return $this->_allowedType s[$mimeType];
    }
    }
    public function getAllowedExten sions() {
    $outputArray = array();
    foreach($this->_allowedType s as $_type) {
    $diff = array_diff($_ty pe, $outputArray);
    $outputArray = array_merge($ou tputArray, $diff);
    }
    return $outputArray;
    }
    }[/code]
    Which could be used like so:
    [code=php]<form method="post" action="?" enctype="multip art/form-data">
    <input type="file" name="imageFile " /><br />
    <input type="file" name="mp3File" /><br />
    <input type="submit" name="submit" value="Upload" />
    </form>
    <?php
    if(isset($_POST['submit'])) {
    require("FileUp load.php");
    try {
    // Create a FileUploader, specifying the upload path in the constructor
    $uploader = new FileUpload("ima ges");

    // Move the image file using the default settings
    $uploader->moveFile("imag eFile");
    echo "<h3>First file uploaded</h3>";

    // Change the the upload path and max file size.
    $uploader->setUploadDir(" music/");
    $uploader->setMaxFileSize (10485760); // 10MB

    // Allow the Mp3 mime and extension and move the Mp3 file.
    $uploader->addAllowedMime Type("audio/mpeg", array("mp3"));
    $uploader->moveFile("mp3F ile");
    echo "<h3>Second file uploaded</h3>";

    // List all allowed mime types and their extensions
    echo "<pre>";
    foreach($upload er->getAllowedMime Types() as $_mime) {
    echo "<strong>{$_mim e}</strong> (";
    $extensions = $uploader->getAllowedMime Extensions($_mi me);
    echo implode(", ", $extensions), ")\n";
    }
    echo "</pre>";

    }
    catch(Exception $ex) {
    // Show error message
    echo "<div><h3>< span style='color: red;'>Exception cought!</font></h3>
    <pre>", $ex->getMessage() , "</pre></div>";
    }
    }
    ?>[/code]

    Leave a comment:


  • Dormilich
    replied
    Originally posted by fjm
    I've been using exceptions now for a couple of weeks. I'm still not 100% certain how an exception is really any different than a if/else statement. I'm sure there is a huge difference but I can't see it yet.
    Originally posted by php.net
    When an exception is thrown, code following the statement will not be executed, and PHP will attempt to find the first matching catch block.
    that means, the Exception will descend through the function/method stack not executing any code until it is caught.

    taking Atli's 2nd example, if an Exception is thrown in task1(), task2() and task3() are not executed and if the Exception is not caught at all a catchable fatal error is thrown.

    Leave a comment:


  • fjm
    replied
    Hold on.... I think I understand what you are saying here Atli.. So you mean.. it isn't good to tie all of my "functional ity" to the constructor because I limit the class. Yes? Here's what I think I am understanding.. By not tying al of my functionality to the constructor and by using getters and setters, I just use those for any current functionality I have or need and then if I require more functionality, I just add more getters and setters. With these getter and setter methods, I don't limit the outside code. Am I right?

    Would it be possible to have you, in your spare time of course, maybe rework this class the way that you think it should be? Because I made that, I would be able to easily follow along on your changes and may be able to ask you more direct questions. The animal class was cool and I gained a lot of insight from it, but it isn't real world stuff. In fact, I looked it over and thought I was doing the same thing whan I made the upload class.

    [QUOTE=Atli;3490 078]You should be able to change every privately declared member or method without any change to how the class is used from the outside.

    Ok, is this what you mean by change privately declared member:

    Code:
    class Color
    {
        private $_color;
    
        public function __construct()
        {
        }
    
        function getColor()
        {
          return $this->_color;
        }
    
        function setColor($color)
        {
          $this->_color = $color;
        }
    }
    $foo = new Color();
    $foo->setColor('red');
    echo $foo->getColor(); // outputs red
    
    $foo->setColor('blue);
    echo $foo->getColor(); //outputs blue
    Notice that I didn't make the get and set methods private because they would throw an error when I tried to access them so I left them public but that isn't right, is it?


    Originally posted by Atli
    constructor parameter list as optional parameters (parameters with default values) or add setters for them.
    Could you elaborate on this a bit? What is a parameter list? Are these the values passed into the constructor?

    Originally posted by Atli
    Better to use aliases that are linked to the members, so the members themselves can be changed without affecting the outside.
    Can you provide a short example of this?

    Originally posted by Atli
    Chaining the private methods, starting at the constructor.
    I kind of felt that was incorrect. I actually made like a trigger method as you had done in your example but was having a problem getting the methods to work correctly. method C was dependant on B and B was dependant on A. When I just listed them like you showed, All of the methods would execute. In order for me to get them to work like that, I had to nest them based on their return values. Was this right?

    Originally posted by Atli
    The constructor should be used to initialize the object, and then public methods should be used to execute whichever functionality of that class you intend to use.
    When you say "initialize the object" what exactly does that mean? What if there are no params required for the constructor? Do you mean that I should initialze all my class members in the constructor?

    Originally posted by Atli
    It's better to have the methods themselves return boolean success values or error numbers.
    Of course, if I did it this way, I'd have to make these methods public, right?

    Originally posted by Atli
    Or better yet; have them throw exceptions.
    I've been using exceptions now for a couple of weeks. I'm still not 100% certain how an exception is really any different than a if/else statement. I'm sure there is a huge difference but I can't see it yet.

    Thanks again Atli for all the help!

    Frank

    Leave a comment:


  • Atli
    replied
    Ok. A few comments on that.

    First: Having all the members private and passing their values via an array into the constructor.

    The first thing that worries me about that is the fact that for this to work, the outside code must be aware of how the private internals of the class work (the private member names).
    Because of this, you can not alter the internals of the class without having to change how the class is used by the outside.
    You should be able to change every privately declared member or method without any change to how the class is used from the outside.

    It's best to identify the values that must be provided and require them as parameters for the constructor.
    Then identify additional values that can be provided and either add them to the constructor parameter list as optional parameters (parameters with default values) or add setters for them.

    Ideally all the required values should have getters, and setters if possible, and the optional values should have both getters and setters.
    That way the outside can at least read the values the class is using, and when possible, change them if that is needed.

    You can of course add global setter method that takes a list of values to be set, but it is not a good idea to tie the keys used by the list to the actual names of the private members. Better to use aliases that are linked to the members, so the members themselves can be changed without affecting the outside.

    Second: Chaining the private methods, starting at the constructor.

    What I mean by this is; having one method trigger another when it is done, leading to the end.

    The trouble with this is that the members are doing more then their names would suggest. For example, your "renameFile " method triggers the "moveFile" method when it is done, which is confusing at best.

    You are basically doing something like this:
    [code=php]
    class example {
    function trigger() {
    // Some code...
    task1();
    }
    function task1() {
    // Some code...
    task2();
    }
    function task2() {
    task3();
    }
    function task3() {
    // Some code...
    }
    // etc...
    }[/code]
    If you plan to have one function trigger several others, like your code does, it would be better to execute them one by one from that function. Like:
    [code=php]
    class example {
    function trigger() {
    task1();
    task2();
    task3();
    }
    function task1() {
    // Some code...
    }
    function task2() {
    // Some code...
    }
    function taks3() {
    // Some code...
    }
    // etc...
    }[/code]
    This way each of the functions in the chain is only responsible for a single task; the one it's name indicates, which makes it re-usable and not tied to the trigger chain.

    This also allows you better control over how they are executed. Exceptions and error codes could be used to alter the chain once it has started.

    Third: Having all the functionality of the class executed by the constructor.

    Even tho I see why you would want this, it's generally not a good idea to have a class act as a function. It limits the class, requiring all future use of it to at least include the functionality the constructor is tied to.

    What if you choose to add FTP support to your FileUpload class later on?
    You can't do that in any decent way, without changing the class in such a way that would break every previous use of it.

    The constructor should be used to initialize the object, and then public methods should be used to execute whichever functionality of that class you intend to use.

    This allows you to add to the class later without having to rewrite the outside code.


    And lastly, I would avoid relying on a public member to relay the class methods error messages. It's better to have the methods themselves return boolean success values or error numbers.
    Or better yet; have them throw exceptions.

    It just makes for much cleaner code.

    That doesn't mean you can't have your class provide past error messages via a public member or a getter. I would just not force users of the class to use them.

    Leave a comment:


  • fjm
    replied
    Ok, I spent the night writing a simple upload script that pretty much demonstrates what I am talking about. My first example sucked but the following is a class that can actually be used. The main goal of the class is to take a file from a user, clean the filename and upload it to the server. There are also options that the user can toggle in an array that's passed to the constructor.

    When I designed this class, I didn't see any other public method that was needed, other than a simple message that lets the user know that something is wrong. It's because of this that I have made all of the members private as well as the methods.

    Please have a look at it and let's discuss how it can be made better. I would also like to know if this is a decent class design.


    Thanks,
    Frank

    Code:
    <?php
    class FileUpload
    {
        private $_upload = array();
        private $_config = array();
        private $_fileName;
        private $_fileMime;
        private $_fileSize;
        private $_fileTemp;
        private $_fileError;
        private $_setFormName   = 'userfile';
        private $_setExtension;
        private $_setNewName;
        private $_setUploadPath = null;
        private $_setMaxSize    = 0;
        public  $message;
    
        public function __construct( array $upload, array $config )
        {
            $this->_upload = $upload;
            $this->_config = $config;
            $this->setOptions();
            $this->initialize();
        }
    
        private function setOptions()
        {
            $opts = $this->_config;
            foreach($opts as $opt => $val)
            {
                if(!empty($val))
                {
                    $this->{$opt} = $val;
                }
            }
        }
    
        private function initialize()
        {
            $upload = $this->_upload;
            $form   = $this->_setFormName;
            if(!empty($upload["$form"]['name']))
            {
                if($this->testUploadDir())
                {
                    $this->_fileName  = $this->_upload["$form"]['name'];
                    $this->_fileMime  = $this->_upload["$form"]['type'];
                    $this->_fileSize  = $this->_upload["$form"]['size'];
                    $this->_fileTemp  = $this->_upload["$form"]['tmp_name'];
                    $this->_fileError = $this->_upload["$form"]['error'];
                    $this->checkExtension();
                }
            }
            else
            {
                $this->message = 'Please Select A File To Upload...';
            }
        }
    
        private function testUploadDir()
        {
            $path = $this->_setUploadPath;
            if(file_exists($path))
            {
                if(!is_readable($path))
                {
                    $this->message = 'Upload Directory Not Readable';
                    return false;
                }
                if(!is_writeable($path))
                {
                    $this->message = 'Upload Directory Not Writeable';
                    return false;
                }
            }
            else
            {
                $this->message = 'Upload Directory Does Not Exist';
                return false;
            }
            return true;
        }
    
        private function cleanFile()
        {
            $temp = $this->_fileName;
            $temp = strtolower($temp);
            $temp = str_replace(' ', '_', $temp);
            $result = '';
            for ($i=0; $i<strlen($temp); $i++)
            {
                if (preg_match("([0-9]|[a-z]|_|.)", $temp[$i]))
                {
                    $result = $result . $temp[$i];
                }
            }
            $this->_fileName = $result;
        }
    
        private function checkExtension()
        {
            $this->cleanFile();
            $file   = $this->_fileName;
            $getExt = substr($file, strpos($file,'.'), strlen($file)-1);
            $setExt = $this->_setExtension;
            if($getExt != $setExt)
            {
                $this->message = 'The File You Attempted To Upload Is Not Allowed.';
                return false;
            }
            $this->checkSize();
        }
    
        private function checkSize()
        {
            $size = $this->_fileSize;
            $max  = $this->_setMaxSize;
            if($max != 0)
            {
                if($size > $max)
                {
                    $this->message = 'The File You Attempted To Upload Is Too Large.';
                    return false;
                }
            }
            $this->renameFile();
        }
    
        private function renameFile()
        {
            $old = $this->_fileName;
            $new = $this->_setNewName;
            if(!empty($new))
            {
                $this->_fileName = $new;
            }
            $this->moveFile();
        }
    
        private function moveFile()
        {
            $temp      = $this->_fileTemp;
            $file      = $this->_fileName;
            $path      = $this->_setUploadPath;
            $error     = $this->_fileError;
            if($error == 0)
            {
                if(move_uploaded_file($temp,$path.$file))
                {
                    $this->message = 'Your File Upload Was Successful.';
                }
                else
                {
                    $this->message = 'Your File Upload Failed While Moving It To Its Permanent Location.';
                }
            }
            else
            {
                $this->message = 'Your File Upload Failed. Errno: '.$error;
            }
        }
    }
    
    $file = $_FILES;
    $config = array('_setFormName'=>'test','_setExtension'=>'.jpg','_setNewName'=>'new.jpg','_setUploadPath'  => './upload/','_setMaxSize'=>'0');
    $test = new FileUpload($file,$config);
    echo $test->message;
    ?>
    <form method="post" action="upload.php" enctype="multipart/form-data">
      <input type="file" name="test" size="25">
      <input type="submit" name="submit" value="Upload">
    </form>

    Leave a comment:


  • fjm
    replied
    Thanks for the advice Markus. I'll do that. I seem to do better with books anyhow.

    Leave a comment:


  • Markus
    replied
    Originally posted by fjm
    There is some really great information here. Thanks for all the help guys. I am going to digest this and respond this weekend when I have a little more free time. I've already read this over once and will re-read it again this weekend before I respond. I'm not going to let this thread die!! :)
    Go to your local library and find some books on programming design patterns / OOP. It doesn't really matter what language they're in; the concepts are relatively the same.

    Leave a comment:


  • fjm
    replied
    There is some really great information here. Thanks for all the help guys. I am going to digest this and respond this weekend when I have a little more free time. I've already read this over once and will re-read it again this weekend before I respond. I'm not going to let this thread die!! :)

    Leave a comment:


  • Markus
    replied
    Originally posted by Atli
    Depends on how you look at it, and might well be a matter of personal preference.

    Would the function be using any class variables?
    Would it even be related to the class itself?
    [...]

    Very well said. Great read :)

    Leave a comment:


  • dlite922
    replied
    Frank,

    To summarize, Classes should sort of "group" certain behavior or functionality together. For formatting an output (ie your hello example) a function would do, but let's say you had a formatter class that contained several of these things and were used through out your application. To this point, one of the core values of OOP is code reuse.

    Also use OOP to define "objects" instead of "functions" . For example your application might have a User class, and ATM class, and a Storage class instead of having function classed like "Deposits", or "Transactio n" classes. These functions will fall into behaviors of the objects themselves, ie

    User::setBalanc e("5000");
    User::changeAdd ress("123 Main");
    ATM::getMenuOpt ions();
    Storage::record Transaction();

    To me, it looks like you're on the right track. As you continue to read and write code, you'll be able to see what the best way to implement classes keeping the values of OOP in mind: Encapsulation, Code Reuse, Divide and concur the problem, and Bug Containment.

    Next topic I recommend for you is Exceptions/Error handling.

    BTW: I'd like to point out this has been the best OOP thread on bytes and see how others could benefit from it.

    See you around!




    Dan

    Leave a comment:


  • Atli
    replied
    Originally posted by fjm
    OK, I understand that. I guess my question is what if a public method really has nothing to do with any of the other methods? Let's say that you have several methods that just draw data from the database and the sql requires a var for the WHERE statement. We would have to pass that variable into the method. I guess what I am trying to say here is that I am not seeing much difference between a regular function and a public method where the public methods sole purpose in life is to return data from a database. Now, if that public function actually called a private function for something, I could see it. Am I wrong?
    Depends on how you look at it, and might well be a matter of personal preference.

    Would the function be using any class variables?
    Would it even be related to the class itself?

    If both are false, then this function might indeed not belong to that class, but rather by itself or in a different class.
    If only the second one is true, the you should consider making it a static method. (A method available even without creating an instance of the class.)

    Like, say, in a classic animal class:
    • A method "speak" would be specific to an instance of the class, using the members for that instance. Clearly belongs to the class.
    • A method "getAllByTy pe" would not be specific to an instance, nor related in any way to any of the other members or methods. It should, however, belong to the animal class, as it does provide functionality relating to the class. (A perfect example of a static method.)
    • A method "setUpAminalDat abase", despite being somewhat related to the Animal class, provides no real functionality for the class itself. It should really belong to a separate, more database related, class... or just in a solo function somewhere.


    I would imagine structuring a Animal class somewhat like:
    [code=php]
    class Animal {
    /** Members **/
    private $type;
    private $name;

    /** Constructors / Destructors **/
    public function __construct($ty pe, $name, $insert=false){
    $this->type = $type;
    $this->name = $name;
    if($insert) {
    $this->addToDatabase( );
    }
    }

    /** Instance methods **/
    public function speak() {
    echo ucfirst($this->getSound()), ", my name is {$this->name}, the {$this->type}. <br>\n";
    }

    private function getSound() {
    switch($this->type)
    {
    default: return "yum yum"; break; // Couldn't think of anything else :P
    case "dog": return "woof woof"; break;
    case "cat": return "meow"; break;
    }
    }

    public function addToDatabase() {
    $sql = "INSERT INTO animals(name, type) VALUES('{$this->name}', '{$this->type}')";
    return @mysql_query($s ql) == true;
    }

    /** Static methods **/
    public static function getAllByType($t ype="dog") {
    $sql = "SELECT name FROM animals WHERE type = '{$type}'";
    if($result = @mysql_query($s ql)) {
    $output = array();
    while($row = mysql_fetch_ass oc($result)) {
    $output[] = new Animal($type, $row['name']);
    }
    return $output;
    }
    else {
    return array();
    }
    }
    }
    ?>[/code]
    To be used like:
    [code=php]<?php
    // Create a dog and a whale
    $cat = new Animal("cat", "Michell"); // Not added to the DB
    $whale = new Animal("whale", "Bob", true); // Added to the DB

    // Get all dogs already in the database
    $dogs = Animal::getAllB yType("dog");

    // Make em all speak
    foreach($dogs as $_dog) {
    $_dog->speak();
    }
    $cat->speak();
    $whale->speak();
    ?>[/code]
    The method "getAllByTy pe" there being a key part of the class, despite the fact that it is detached from the rest of it, and that it only really performs a simple database query.
    Originally posted by fjm
    In your example, if you wanted to use another public method that has NOTHING AT ALL to do with any of the other methods, what do you do? Do you make another class for it? Say for example that you wanted to have another function that said goodbye but instead of using the person's first name, you would be required to use their last name.
    Even if the method does nothing but take a name and echo it back to you in a "You have been logged out, $lastname. Goodbye." sort of way, If it is related to the class (a User class, in this scenario), then it should belong to it.
    Even tho it may seem pointless to add this to the class, it may become imporant later on.

    Imagine if, after using this goodbye method in 200 pages, the requirements are changed. You are now required to print a language specific message depending on the language set in the constructor. It's a simple change if the method was in the class from the start. Not so simple if it was a rogue function.

    Originally posted by fjm
    OK. This goes back to what I was saying about the constructor. Instead of using a public method to do this, I thought the constructor was supposed to handle this which was why I designed it that way.
    The constructor is only supposed to handle the initialization of the class; making sure all the members are set and ready to be used by the methods.
    Likewise, destructors are meant to unset members and free resources.

    This includes tasks like opening/closing database connections and running security algorithms. Anything not meant to be accessed by the outside, but needs to be run every time the object is created.

    You can of course go beyond that if you want to, but be very careful. Bloated constructors are never good.
    Providing a public method is usually better, and much cleaner.

    It's also common to use constructor parameters as shortcuts to execute commonly used methods. (Like I do in the Animal example above.)

    Originally posted by fjm
    I have been doing a bit more reading on this and have looked over the link on interface. Not certain exactly how that would help me out just yet but of course it's because I don't fully inderstand it yet. I will re-read it and look for some additional info on that.
    Using Interfaces isn't vital, nor really needed, in any one class.
    Especially not since the class itself serves as a blueprint for how the class is meant to be used.

    Interfaces are used to describe a group of classes, who are all meant to provide the same... well, interface to be used by other parts of the code.

    Kind of like cars... They all share the same interface. A wheel, a gear shift, pedals, etc..
    We can always be sure every car is built around this same basic interface, even tho they all implement it differently beneath the surface, and most of them add additional functionality on top.

    This is exactly what interfaces do for classes. They ensure that classes provide at least the methods specified in the Interfaces they implement.
    They help maintain the integrity of the classes from a design perspective, but add little to how the classes themselves are actually built or used.
    (Any class built upon an interface could simply remove the implements keyword and function exactly the same afterwards.)
    Last edited by Atli; May 21 '09, 04:16 AM. Reason: Spelling

    Leave a comment:


  • Dormilich
    replied
    Originally posted by fjm
    I have been doing a bit more reading on this and have looked over the link on interface. Not certain exactly how that would help me out just yet but of course it's because I don't fully inderstand it yet.
    interfaces are a way for an outside developer to use your classes (think of the interface as API). conforming to an interface lets you use a class, without knowing the (internal) structure of this class.

    when implementing an interface in a class definition, you make sure, that the required functionality is given (otherwise there will be an error).

    an example may be found here

    interfaces can also be used to check if an object has a (set of) public method you want to use later. (esp. when there are user defined classes involved)

    (Atli can probably descibe that better…)

    Leave a comment:


  • fjm
    replied
    Atli,

    I have been doing a bit more reading on this and have looked over the link on interface. Not certain exactly how that would help me out just yet but of course it's because I don't fully inderstand it yet. I will re-read it and look for some additional info on that.

    Regarding post #5, in your example... Your private method acts as a mutator and the public getHello acts as an accessor, right?

    Could we say that by having a mutator and accessor that we are encapsulating the data within the class? That's what it looks like to me. Am I right?

    Leave a comment:

Working...