Hello and welcome to our community! Is this your first visit?
Register
Enjoy an ad free experience by logging in. Not a member yet? Register.
Results 1 to 5 of 5
  1. #1
    Senior Coder chump2877's Avatar
    Join Date
    Dec 2004
    Location
    the U.S. of freakin' A.
    Posts
    2,795
    Thanks
    19
    Thanked 156 Times in 147 Posts

    C#-like properties in PHP

    I'm toying around with the idea of implementing C#-like properties in PHP using magic methods (__get and __set). Up until now, I generally have used the following format for getter/setter "properties":

    PHP Code:
    class SampleClass
    {
        
    // Fields
        
    private $_field 'value';

        
    // Pseudo-Properties
        
    public function GetField()
        {
            return 
    $this->_field;
        }
        public function 
    SetField($val)
        {
            
    $this->_field $val;
        }
    }

    $sc = new SampleClass();
    echo 
    $sc->GetField() . "<br />";
    $sc->SetField('another value');
    echo 
    $sc->GetField() . "<br /><br />"
    ...but I've never been entirely comfortable with that format. I've always preferred C#'s implementation of getters/setters.

    So I came up with the following workaround for PHP:

    PHP Code:
    class BaseClass
    {
        
    // Invoke C#-like Properties
        
    public function __get($name)
        {
            try
            {
                if (
    method_exists($this$name))
                {
                    return 
    $this->$name();
                }
                else
                {
                    throw new 
    Exception('Undefined property '.get_class($this).'::'.$name);
                }
            }
            catch (
    Exception $ex)
            {
                
    $origin end($ex->getTrace());
                die(
    "<br /><b>Exception: " $ex->getMessage() . " in " $origin['file'] . " on line " $origin['line'] . "</b>");
            }
        }

        public function 
    __set($name$value)
        {
            try
            {
                if (
    method_exists($this$name))
                {
                    
    $this->$name($value);
                }
                else
                {
                    throw new 
    Exception('Undefined property '.get_class($this).'::'.$name);
                }
            }
            catch (
    Exception $ex)
            {
                
    $origin end($ex->getTrace());
                die(
    "<br /><b>Exception: " $ex->getMessage() . " in " $origin['file'] . " on line " $origin['line'] . "</b>");
            }
        }
    }

    class 
    SampleClass2 extends BaseClass
    {
        
    // Fields
        
    private $_field 'SampleClass2::_field value';
        private 
    $_anotherField 'SampleClass2::_anotherField value';

        
    // Pseudo-Property Declarations
        
    protected function Field($val=null)
        {
            if (
    is_null($val))
            {
                
    //getter
                
    return $this->_field;
            }
            else
            {
                
    //setter
                
    $this->_field $val;
            }
        }

        protected function 
    AnotherField($val=null)
        {
            if (
    is_null($val))
            {
                
    //getter
                
    return $this->_anotherField;
            }
            else
            {
                
    //setter
                
    $this->_anotherField $val;
            }
        }
    }

    class 
    SampleClass3 extends SampleClass2
    {
        
    // Fields
        
    private $_field 'SampleClass3::_field value';

        
    // Pseudo-Property Declarations
        
    protected function Field($val=null)
        {
            if (
    is_null($val))
            {
                
    //getter
                
    return $this->_field;
            }
            else
            {
                
    //setter
                
    $this->_field $val;
            }
        }
    }

    $sc = new SampleClass3();
    echo 
    $sc->Field "<br />";
    $sc->Field 'another SampleClass3::_field value';
    echo 
    $sc->Field "<br />";
    echo 
    $sc->AnotherField "<br /><br />";
    //echo $sc->Field2; // throws exception

    $sc = new SampleClass2();
    echo 
    $sc->Field "<br />";
    $sc->Field 'another SampleClass2::_field value';
    echo 
    $sc->Field "<br />";
    echo 
    $sc->Field2// throws exception 
    This more or less requires a rigid naming convention for different language constructs, i.e.:

    1) Field example -- _field (underscore followed by noun, camel cased)
    2) Property example -- Field (Pascal cased corresponding field name)
    3) Method example -- DoSomething() (verb in function name)

    ...but I don't really view that as a "bad" thing.

    I'm interested in hearing any feedback regarding this approach. Suggestions, criticisms, etc. Thanks.
    Last edited by chump2877; 02-01-2012 at 10:40 PM.
    Regards, R.J.

    ---------------------------------------------------------

    Help spread the word! Like my YouTube-to-Mp3 Conversion Script on Facebook !! :)
    [Related videos and tutorials are also available at my YouTube channel and on Dailymotion]
    Get free updates about new software version releases, features, and bug fixes!

  • #2
    God Emperor Fou-Lu's Avatar
    Join Date
    Sep 2002
    Location
    Saskatoon, Saskatchewan
    Posts
    16,994
    Thanks
    4
    Thanked 2,662 Times in 2,631 Posts
    I posted something similar to this a few years ago as well. Chaining to existing methods makes the most sense. The only real difference I did is that I took accessor / mutator syntax with getX and setX instead, and chained that to the X.

    You shouldn't try/catch within a method like the way you are here. Just throw. Let the caller decide what they want to do with it. Create a custom exception if you like, something like NoSuchPropertyException just to make it a little clearer.

  • #3
    Senior Coder chump2877's Avatar
    Join Date
    Dec 2004
    Location
    the U.S. of freakin' A.
    Posts
    2,795
    Thanks
    19
    Thanked 156 Times in 147 Posts
    Quote Originally Posted by Fou-Lu View Post
    I posted something similar to this a few years ago as well. Chaining to existing methods makes the most sense. The only real difference I did is that I took accessor / mutator syntax with getX and setX instead, and chained that to the X.

    You shouldn't try/catch within a method like the way you are here. Just throw. Let the caller decide what they want to do with it. Create a custom exception if you like, something like NoSuchPropertyException just to make it a little clearer.
    Thanks for the response, Fou-Lu. Sounds like my approach here is reasonable enough then

    Re: the try/catch block stuff, the reason I am handling the exception inside the __get/__set methods is because this line:

    PHP Code:
    throw new Exception('Undefined property '.get_class($this).'::'.$name); 
    ...if the exception is not handled, outputs:

    Exception: Undefined property SampleClass2::Field2 in C:\xampp\htdocs\test\getter_setter_test.php on line 37
    ...which points to the line number inside __get() instead of for this line:

    PHP Code:
    echo $sc->Field2// throws exception 
    ...which, while expected, isn't very helpful...And I doubt I will put every property manipulation inside a try/catch block, i.e.:

    PHP Code:
    try { echo $sc->Field2; }
    catch (
    Exception $ex) { // do something } 
    ...it doesn't make sense to me to do that -- a property is just a missing language construct in PHP, so why should the burden fall on the user to handle exceptions for potentially undefined properties (to get reliable line numbers for the errors)? In my opinion, PHP's default error handling should handle basic errors like that -- so the try/catch block inside __get and __set is my way of simulating that response (and for generating accurate line numbers for unhandled exceptions).

    It's not a "pretty" approach, but simulating C# properties in PHP isn't a conventional approach either (in terms of PHP OOP)...

    Is there a better way to output accurate line numbers for unhandled exceptions without using try/catch block in __get/__set? If there is, then I would definitely prefer it.
    Regards, R.J.

    ---------------------------------------------------------

    Help spread the word! Like my YouTube-to-Mp3 Conversion Script on Facebook !! :)
    [Related videos and tutorials are also available at my YouTube channel and on Dailymotion]
    Get free updates about new software version releases, features, and bug fixes!

  • #4
    God Emperor Fou-Lu's Avatar
    Join Date
    Sep 2002
    Location
    Saskatoon, Saskatchewan
    Posts
    16,994
    Thanks
    4
    Thanked 2,662 Times in 2,631 Posts
    Getting the line number is simply a matter of diving into the stack trace. The last level of the trace is where the initial call was placed. I don't really see this as being a huge issue overall since the same behaviour would occur if you were to pull this throw another object or function.
    The only way I can think of to clean it up without forcing the handling within a catch itself is to use a function to display the output and chain it to a custom exception for NoSuchPropertyException. Something like this:
    PHP Code:
    function showException(Exception $ex)
    {
        
    $iLine $ex->getLine();
        
    $sMessage $ex->getMessage();
        
    $sMessage = !empty($sMessage) ? $sMessage 'No Message Provided';
        if (
    $ex instanceof NoSuchPropertyException)
        {
            if (
    count($trace $ex->getTrace()))
            {
                
    $call array_pop($trace);
                
    $iLine $call['line'];
            }
        }
        return 
    sprintf('%s: "%s" on line %d' PHP_EOLget_class($ex), $sMessage$iLine);
    }

    class 
    NoSuchPropertyException extends Exception
    {
    }

    class 
    C
    {
        private 
    $test 'test';

        public function 
    getTest()
        {
            return 
    $this->test;
        }
        public function 
    setTest($val)
        {
            
    $this->test $test;
        }
        public function 
    __get($var)
        {
            
    $method 'get' $var;
            if (
    method_exists($this$method))
            {
                return 
    $this->$method();
            }
            else
            {
                throw new 
    NoSuchPropertyException('No such property');
            }
        }
    }

    function 
    ccall(C $c$var)
    {
        return 
    $c->$var;
    }

    $c = new C();
    try
    {
        print 
    ccall($c'test2');
    }
    catch (
    Exception $ex)
    {
        print 
    showException($ex);

    This should show the exception occurred on line 60 instead of 47.

    Edit:
    BTW, depending on exactly what you want to show depends on where you take it out of. If I used array_shift instead I would have gotten the location of the ccall instead of where ccall was called.
    Last edited by Fou-Lu; 02-05-2012 at 04:59 PM.

  • #5
    Senior Coder chump2877's Avatar
    Join Date
    Dec 2004
    Location
    the U.S. of freakin' A.
    Posts
    2,795
    Thanks
    19
    Thanked 156 Times in 147 Posts
    That gets me the correct line number, but at the expense of having to put every property get/set accessor in a try-catch block in the program's main execution code...which, again, I think is an unnecessary burden on the programmer (who shouldn't have to be responsible for handling that kind of error) and actually creates more bloated code (in the form of a scourge of try-catch blocks)....That's my opinion of course...so if I have to break the rules in this particular instance (catch an exception immediately after it is thrown in __get/__set), then I guess I can live with that...
    Regards, R.J.

    ---------------------------------------------------------

    Help spread the word! Like my YouTube-to-Mp3 Conversion Script on Facebook !! :)
    [Related videos and tutorials are also available at my YouTube channel and on Dailymotion]
    Get free updates about new software version releases, features, and bug fixes!


  •  

    Posting Permissions

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