...

View Full Version : C#-like properties in PHP



chump2877
02-01-2012, 11:38 PM
I'm toying around with the idea of implementing C#-like properties (http://msdn.microsoft.com/en-us/library/w86s7x04(v=vs.100).aspx) in PHP using magic methods (__get and __set). Up until now, I generally have used the following format for getter/setter "properties":


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:


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 (http://en.wikipedia.org/wiki/CamelCase) 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.

Fou-Lu
02-02-2012, 06:16 AM
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.

chump2877
02-05-2012, 09:26 AM
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:


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:


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.:


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.

Fou-Lu
02-05-2012, 05:54 PM
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:


function showException(Exception $ex)
{
$iLine = $ex->getLine();
$sMessage = $ex->getMessage();
$sMessage = !empty($sMessage) ? $sMessage : 'No Message Provided';
if ($ex instanceof NoSuchPropertyException)
{
if (0 < count($trace = $ex->getTrace()))
{
$call = array_pop($trace);
$iLine = $call['line'];
}
}
return sprintf('%s: "%s" on line %d' . PHP_EOL, get_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.


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.

chump2877
02-19-2012, 10:50 AM
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...



EZ Archive Ads Plugin for vBulletin Copyright 2006 Computer Help Forum