SitePoint Sponsor

User Tag List

Results 1 to 16 of 16
  1. #1
    SitePoint Wizard silver trophybronze trophy Cups's Avatar
    Join Date
    Oct 2006
    Location
    France, deep rural.
    Posts
    6,869
    Mentioned
    17 Post(s)
    Tagged
    1 Thread(s)

    Use of Exceptions in my model

    In PHP5 I am feeling my way around using Exceptions to trap errors that should not really happen in my classes. I find I am doing this in order to unit test the method.

    In this simple example the query will not go ahead unless it is passed an INT. Higher up in the app I would expect to check that only an INT can be passed, or should be passed, an a user gets passed a message, or gets re-routed.

    But at this stage I find this a very cheap and cheerful way to do some basic validation, and it gives me some confidence that even if I overlook my validation higher up, this will at least act as a safety net.

    PHP Code:
    class press_releases{


    ....

    function 
    collectMetaListByState($state) {
            if(!
    is_int($state)){
                throw new 
    Exception('You can only collect by state number');
            }

            
    $rel=$this->PDO->prepare(" ... ");
            
    $rel->execute(); 
            
    $data $rel->fetchAll();
            
    $data_rows$rel->rowCount();

            if ( 
    $data_rows == ) { // this 
                
    throw new Exception('No PRs in that state');
            
            }
        return 
    $data;
    //method

    //class


    //then in the unit test (nod to Luke Redpath)

    public function testExceptionWhenSendingNonIntValueForSinglePR() {

        try {
          
    $this->prm->collectMetaListByState('eek');
          
    $this->fail();
        } catch(
    Exception $e) {
          
    $this->pass();
       }

    It feels as though unit testing tends to make me think like this.

    My questions are:

    Is this use of Exceptions a valid way to handle these kinds of errors in my domain model?

    Should I bother doing this at the domain model level?

    Would you tend to extend Exceptions, say create an Exception for each model?

    If so, would you give it any extra responsibilities, and would you use a particular pattern to hook your Exception handler to your model?

  2. #2
    SitePoint Enthusiast shref's Avatar
    Join Date
    Nov 2004
    Location
    Egypt, Alexandria
    Posts
    99
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by Cups
    In this simple example the query will not go ahead unless it is passed an INT. Higher up in the app I would expect to check that only an INT can be passed, or should be passed, an a user gets passed a message, or gets re-routed.
    At the top level of your app, you can add Input filter which handles the validation of the user input. also in the internal scope, throwing exceptions is important to validate the data (how would you make sure that the data passed to your method weren't changed ?!).

    PHP Code:
    //then in the unit test (nod to Luke Redpath) 
      
     
    public function testExceptionWhenSendingNonIntValueForSinglePR() { 
      
         try { 
           
    $this->prm->collectMetaListByState('eek'); 
           
    $this->fail(); 
         } catch(
    Exception $e) { 
           
    $this->pass(); 
        } 
     } 
    you can subclass your unit testing class and add a new method to check for thrown exceptions. I think this is better practice.

    PHP Code:
     public function testExceptionWhenSendingNonIntValueForSinglePR() { 
      
         
           
    $this->isNotThrowingException($this->prm->collectMetaListByState('eek'),  'Exception'); 
           
        } 
     } 
    Would you tend to extend Exceptions, say create an Exception for each model?
    yes, creating a new Exception class for each component can make your life easier when debuging or testing . adding more responsibilities to your exception class depends on what data you want to be attached to the excption when thrown.

    thank you.
    Shreef
    blog: shreef.com
    twitter: @shreef

  3. #3
    SitePoint Wizard silver trophybronze trophy Cups's Avatar
    Join Date
    Oct 2006
    Location
    France, deep rural.
    Posts
    6,869
    Mentioned
    17 Post(s)
    Tagged
    1 Thread(s)
    Quote Originally Posted by shref
    You can subclass your unit testing class and add a new method to check for thrown exceptions. I think this is better practice.
    PHP Code:
    public function testExceptionWhenSendingNonIntValueForSinglePR() { 
            
    $this->isNotThrowingException($this->prm->collectMetaListByState('eek'),  'Exception'); 
          
        } 
     } 
    Thanks for your reply, but I just cant seem to work out how to defer the Exception handling to another test in simple test... it just fires off the Exeption as soon as it is called. Do you have an example of what you mean by this particular kind of subclass? I mean I imagined it to be something that handles the testing that an Exception was triggered.

  4. #4
    SitePoint Enthusiast shref's Avatar
    Join Date
    Nov 2004
    Location
    Egypt, Alexandria
    Posts
    99
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Ops, for sure this will fire the exception before going in 'isNotThrowingException',
    here is a full working sample using "simple test"

    PHP Code:
      <?php
     error_reporting
    (E_ALL E_NOTICE);
     
      
    /**/  require_once('../../unit_tester.php');
      
    /**/  require_once('../../reporter.php');
     
     class 
    BadFoodException extends Exception { }
     
     class 
    Cat
     
    {
     
         function 
    eat($word)
         {
             if( 
    $word != 'fish' )
             {
                 throw new 
    BadFoodException("this is a bad food");
             }
             
         }
     }
     
     
     class 
    MyUnitTester extends UnitTestCase
     
    {
     
         function 
    MyUnitTester($name=false)
         {
             
    $this->UnitTestCase($name);
         }
     
         
    /**
          * checks if an exception was thrown from a method
          *@param string method name
          *@param array args to be passed to the method
          *@param string the name of the exception
          *@param object the methods object
          */
         
    function isNotThrowingException($method$args=null $exception='Exception', &$object=null)
         {
             if ( 
    $object === null )
                 
    $object = &$this;
                 
                 try
                 {
                     
    call_user_func_array(array(&$object$method), $args);
                 }
                 catch(
    Exception $ex)
                 {
                     if (
    get_class($ex) != $exception)
                         
    $this->fail('Exception [' get_class($ex) .'] was thrown from ' .
                                     
    get_class($object) . '::' $method '(' 
                                     
    implode(', ' $args) . ')  #message: ' $ex->getMessage()
                                     );
                 }
             
            
    $this->pass();
         }
         
     }
     
     
     
     
     class 
    CatTest extends MyUnitTester
     
    {
     
     
         function 
    testWhatSheEats()
         {
             
    $cat = new Cat() ;
             
             
    $this->isNotThrowingException'eat', array('pizza'), 'BadFoodException',$cat );
         }
     
     }
     
     
     
    $test = new CatTest();
     
    $test->run( new HtmlReporter() );
     
     
    ?>
    Shreef
    blog: shreef.com
    twitter: @shreef

  5. #5
    SitePoint Wizard silver trophybronze trophy Cups's Avatar
    Join Date
    Oct 2006
    Location
    France, deep rural.
    Posts
    6,869
    Mentioned
    17 Post(s)
    Tagged
    1 Thread(s)
    That a nice example, thanks for your patience Shref, but I want, on an expected Exception being thrown - a unit test "pass" - I dont want to see the exception.

    Which seems to be the opposite of what you have, I tried making a test:

    $this->isReallyThrowingException( 'eat', array('pizza'), 'BadFoodException',$cat );

    But I hit the same problem, the exception, on catch is echoed to the screen.

    My own dumbass problem, trying to learn unittesting and Exceptions at the same time, this is why I found Lukes solution very simple and neat.

    I now see what you mean about subclassing the unittest class, I really thought you meant adding a new method something along the lines of assertException();


  6. #6
    SitePoint Enthusiast shref's Avatar
    Join Date
    Nov 2004
    Location
    Egypt, Alexandria
    Posts
    99
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    I think that 'assertException()' will be included in SimpleTest 2.0 . IMHO using a differenet name like 'isNotThrowingException()' is better now (forward compatibility).

    That a nice example, thanks for your patience Shref, but I want, on an expected Exception being thrown - a unit test "pass" - I dont want to see the exception.

    Which seems to be the opposite of what you have, I tried making a test:

    $this->isReallyThrowingException( 'eat', array('pizza'), 'BadFoodException',$cat );

    But I hit the same problem, the exception, on catch is echoed to the screen.
    okay, here is [revision 1] :
    - fixed : isNotThrowingException()
    - added : isThrowingException()

    PHP Code:
    <?php
    error_reporting
    (E_ALL E_NOTICE);

     
    /**/  require_once('../../unit_tester.php');
     
    /**/  require_once('../../reporter.php');

    class 
    BadFoodException extends Exception { }

    class 
    Cat
    {

        function 
    eat($word)
        {
            if( 
    $word != 'fish' )
            {
                throw new 
    BadFoodException("this is a bad food");
            }
            
        }
    }


    class 
    MyUnitTester extends UnitTestCase
    {

        function 
    MyUnitTester($name=false)
        {
            
    $this->UnitTestCase($name);
        }

        
    /**
         * checks if an exception was thrown from a method
         *@param string method name
         *@param array args to be passed to the method
         *@param string the name of the exception
         *@param object the methods object
         */
        
    function isNotThrowingException($method$args=null $exception='Exception', &$object=null)
        {
            if ( 
    $object === null )
                
    $object = &$this;
                
                try
                {
                    
    call_user_func_array(array(&$object$method), $args);
                }
                catch(
    Exception $ex)
                {
                    if (
    get_class($ex) == $exception)
                        return 
    $this->fail('the exception [' get_class($ex) .'] was thrown from ' .
                                    
    get_class($object) . '::' $method '(' 
                                    
    implode(', ' $args) . ')  #message: ' $ex->getMessage()
                                    );
                }
            
           
    $this->pass();
        }
        
        
        
        
        
    /**
         * this tester reports failure when $exception wasn't thrown
         *@param string method name
         *@param array args to be passed to the method
         *@param string the name of the exception
         *@param object the methods object
         */
        
    function isThrowingException($method$args=null $exception='Exception', &$object=null)
        {
            if ( 
    $object === null )
                
    $object = &$this;
                
                try
                {
                    
    call_user_func_array(array(&$object$method), $args);
                }
                catch(
    Exception $ex)
                {
                    if (
    get_class($ex) == $exception)
                        return 
    $this->pass();
                        
                }
                
           
    $this->failget_class($object) . '::' $method '(' 
                        
    implode(', ' $args) . ')  didn\'t throw 
                        exception [' 
    $exception '] '
                       
    ); 
           
        }
        
    }




    class 
    CatTest extends MyUnitTester
    {


        function 
    testWhatSheEats()
        {
            
    $cat = new Cat() ;
            
            
    // should Pass
            
    $this->isNotThrowingException'eat', array('Hot Fish'), 'VeryHotFoodException',$cat );
            
            
    // should Fail
            
    $this->isNotThrowingException'eat', array('pizza'), 'BadFoodException',$cat );
            
            
    //should Pass
            
    $this->isThrowingException'eat', array('pizza'), 'BadFoodException',$cat );
            
            
    // should Fail
            
    $this->isThrowingException'eat', array('pizza'), 'VeryHotFoodException',$cat );
            
        
        }

    }


    $test = new CatTest();
    $test->run( new HtmlReporter() );

    ?>
    let me know if you met any issue.
    Shreef
    blog: shreef.com
    twitter: @shreef

  7. #7
    SitePoint Wizard silver trophybronze trophy Cups's Avatar
    Join Date
    Oct 2006
    Location
    France, deep rural.
    Posts
    6,869
    Mentioned
    17 Post(s)
    Tagged
    1 Thread(s)
    I did a better search and turned up this information.

    If you can get the cvs version of simpletest then it includes an expectException() test, as detailed in this posting here

  8. #8
    SitePoint Enthusiast shref's Avatar
    Join Date
    Nov 2004
    Location
    Egypt, Alexandria
    Posts
    99
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by Cups
    I did a better search and turned up this information.

    If you can get the cvs version of simpletest then it includes an expectException() test, as detailed in this posting here
    thank you, I didn't know about that.

    Quote Originally Posted by lastcraft
    The CVS version is pretty stable (it's the one I use).
    maybe I have to upgrade now.

    Best regards
    Shreef
    blog: shreef.com
    twitter: @shreef

  9. #9
    SitePoint Wizard silver trophybronze trophy Cups's Avatar
    Join Date
    Oct 2006
    Location
    France, deep rural.
    Posts
    6,869
    Mentioned
    17 Post(s)
    Tagged
    1 Thread(s)

    simpletest unit test Exceptions Exception error

    Just a follow up for any other poor non-cvs sucker on win32 (like me), who might also be thrashing around... I believe there may be one or two of us...

    Download and install a cvs client. I used tortoiseCVS.

    Go to the simpletest cvs repository.

    Stick the credentials into Tortoise that it tells you, and checkout the cvs version of simpletest. Read the file HELP_MY_TESTS_DONT_WORK_ANYMORE (though all of mine seem to atm)

    Then you can do this with Shrefs example.

    PHP Code:
    <?php

     
    require_once('simpletest/unit_tester.php');
     require_once(
    'simpletest/reporter.php');

    class 
    BadFoodException extends Exception { }
    class 
    HotFoodException extends Exception { }

    class 
    Cat
    {
        function 
    eat($word)
        {
            if( 
    $word != 'fish' )
            {
                throw new 
    BadFoodException("this is a bad food");
            }
        }
    }

    class 
    CatTest extends UnitTestCase
    {
    function 
    testWhatSheEats()
       {
       
    $cat = new Cat() ;
       
    $this->expectException();  
       
    $cat->eat('pizza');  // bad food = a pass
       
    }
    }

    $test = new CatTest();
    $test->run( new HtmlReporter() );

     
    /**
     * optionally tell it the exception to expect
     * 
     * $this->expectException('HotFoodException');  // in this case a fail, wrong exception type
     */
    ?>
    Hope that helps someone else.

  10. #10
    SitePoint Enthusiast Silverhawk's Avatar
    Join Date
    Sep 2003
    Location
    Malaysia
    Posts
    92
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Aren't exceptions only supposed to be used for errors you do not expect to happen? For example checking for database connection would be a good use for an exception because you expect the connection to work (or your application probably wouldn't function).

    However, input validation doesn't seem like a proper use of exceptions. We expect users to input bad data.

  11. #11
    SitePoint Wizard dreamscape's Avatar
    Join Date
    Aug 2005
    Posts
    1,080
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by Silverhawk
    Aren't exceptions only supposed to be used for errors you do not expect to happen?
    Exceptions occur when a method encounters something that it does not know how to deal with, so it throws an Exception hoping that someone higher up the chain does know how to handle it.

    The whole question comes down to responsibility; whose responsibility is it to handle the error? If a cat is fed the wrong kind of food, is it up to the cat to figure out what to do about it, or is it up to the person feeding the cat? I'd tend to say the latter. By throwing an exception, the cat is telling the person who fed it that the food is bad, and that person can then decide what to do about it, if anything.

    IMO, I think this is an acceptable use of exceptions.

    If you follow the "what you expect to happen" camp when it comes to exception use, then I think this is very vague and extremely subjective if you think about it in terms of what you personally expect or what the application as a whole expects. It would be better IMO to think about it in terms of "what the method expects to happen." And certainly I don't think that a cat expects that it will be fed bad food. It may be necessary to call animal rescue if that is the case.
    <.smarter.web.development.>
    PHP Stuff: Plexus | Chocolate (BDD Framework... coming soon)
    Graphite

  12. #12
    simple tester McGruff's Avatar
    Join Date
    Sep 2003
    Location
    Glasgow
    Posts
    1,690
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by Silverhawk
    Aren't exceptions only supposed to be used for errors you do not expect to happen? For example checking for database connection would be a good use for an exception because you expect the connection to work (or your application probably wouldn't function).

    However, input validation doesn't seem like a proper use of exceptions. We expect users to input bad data.
    There's a definite smell surrounding exceptions. Normally an object must hold a reference to another in order to send a message to it but exceptions create a kind of wormhole in the design with an ambiguous exit (ie catch point). Depending how the app is assembled, different catchers could receive a thrown object - or maybe none at all. It could be hard to figure out who the catcher is since there is no trail of references to follow. Exceptions are (in part) Observer but without a "registerObserver()" step.

    Note that catchers don't just decide what to do next: any code between throw point and catch point does not execute. Moving the catch point can change what gets cut out.

    So: throwing an exception means that an untracable object will cut an unknown chunk of code out of the normal script execution before carrying out an unknown action.

    Great.

  13. #13
    SitePoint Wizard silver trophybronze trophy Cups's Avatar
    Join Date
    Oct 2006
    Location
    France, deep rural.
    Posts
    6,869
    Mentioned
    17 Post(s)
    Tagged
    1 Thread(s)
    I share your feeling about there being something wrong about exceptions. I think it would be very tempting to use them as part of flow control...

    Like silverhawk says, no database? throw exception - what? and redirect the user?

    Still, they do have a place, and as I said in my OP I am juggling with them. The interesting feature of them for me (at my stage of learning) is that I have now discovered how easy they are to unit test:

    $this->expectException().

    As I have read here, the event causing an Exception should be caught and handled properly before it happens. Agreed.

    But, is it therefore plain wrong to write Exception catching code in classes, solely for the purpose of easily Unittesting them?
    PHP Code:
    function collectMetaListByState($state) { 

            if(!
    is_int($state)){ 
                throw new 
    Exception('Bad coding happened! Still, you can only collect by state number'); 
            } 

    // then in the test:

      
    $this->expectException(); 
    In this case and I am working on a domain model, and really havent worked out exactly how I going to access this model (will it be mvc, will I use a framework?) - When I practice the break it/fix it - redline/greenline mantra, this seems like a piece of, well, pie.

    (Or perhaps I should just make and test my Observer and have done with it.)

  14. #14
    Non-Member
    Join Date
    Jan 2003
    Posts
    5,748
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Nothing wrong with the use of Exceptions; They are just another tool to aid the developer and if used properly in the first -BEEP- place, then the benifits are obvious.

    At least they should be; Only when there is abuse (much like with any other tool) is there ever a problem; Don't fault a language specification because of ill use by certain developers who are incapable of proper software engineering.

    Great.

  15. #15
    simple tester McGruff's Avatar
    Join Date
    Sep 2003
    Location
    Glasgow
    Posts
    1,690
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by Cups
    But, is it therefore plain wrong to write Exception catching code in classes, solely for the purpose of easily Unittesting them?
    I wouldn't say it's wrong. You need experience living and working with a coding technique before you can really understand all the fine points and I don't have that. I've barely dipped my toe in php5.

    It's a readability issue as much as anything. I'm uneasy sending messages without holding a reference to the recipient. Is this going to be hard to understand and debug? A complex application could be throwing exceptions by the dozen - all broadcast on the same channel. Hints help but a default, catch-all "Exception" in the wrong place could put a spanner in the works.

    As for testing I'd agree it's very easy to test a throw but I'd be more worried about catches. That's where all the action is. Tests will have to assert certain types of exception are caught - and perhaps assert that other types can pass through uncaught. Catchers don't just decide what to do next: the placement of catch points also decides what *doesn't* execute. Something else to test.

  16. #16
    simple tester McGruff's Avatar
    Join Date
    Sep 2003
    Location
    Glasgow
    Posts
    1,690
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by Dr Livingston
    Don't fault a language specification because of ill use by certain developers who are incapable of proper software engineering.
    I should acknowledge your greater expertise in this field.


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
  •