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 4 of 4
  1. #1
    New Coder
    Join Date
    May 2005
    Location
    Leeds, UK
    Posts
    83
    Thanks
    1
    Thanked 0 Times in 0 Posts

    Enforcing class property types

    I've got a financial system where I'd like to make many financial properties into instances of a Charge class, which encapsulate tax information etc, rather than constantly defining vat/ex vat variables & properties.

    What I'd like to do is allow such properties to be set as normal numbers, partly for ease of coding, and partly for compatibility with legacy code. What would be the best way to do this?

    I could use magic functions, I guess:

    Code:
    class Charge
    {
    	public $net;
    	public $gross;
    	public $vat;
    	public $vatRate = 0.2;
    	
    	public function __construct($net)
    	{
    		$this->net = $net;
    		$this->vat = round($this->net*$this->vatRate,2);
    		$this->gross = $this->net + $this->vat;
    	}
    }
    
    class Myclass
    {
    	private $price;
    	
    	public function __set($name,$val)
    	{
    		if($name=="price") $this->setPrice($val);
    	}
    
    	public function setPrice($price)
    	{
    		//enforce price into charge class
    		if(! $price instanceof Charge) $price = new Charge($price);
    		$this->price = $price;
    	}
    }
    
    $class = new MyClass();
    $class->price=3;
    but I've heard magic functions are pretty slow, also, to get the magic function to work I'd either need to not declare my variable initially (not keen on that), or declare it as private, in which case the magic function would work when reference from outside the class, but not when referenced from within the class or it's subclasses.

    Or I could just declare it as private and make sure the setter is used explicitly at all times.

    What would be a good approach to this?

    **EDIT**
    I just came up with another idea - declare all class properties prepended with an underscore eg private $_price;
    You can reference properties in the normal way ($class->price) and, as the property will never exist, will always refer to the magic functions. The magic function then knows to access the property usng the underscore eg
    Code:
    private $_price;
    public function __set($name,$val)
    	{
    		if($name=="price") $this->_price = new Charge($val);//for example
    	}
    This at least gets around the issue of using a consistent reference when referring from within this class or subclasses. Still got a possible speed issue, though.
    Last edited by hessodreamy; 08-17-2012 at 03:55 PM.

  • #2
    God Emperor Fou-Lu's Avatar
    Join Date
    Sep 2002
    Location
    Saskatoon, Saskatchewan
    Posts
    16,978
    Thanks
    4
    Thanked 2,659 Times in 2,628 Posts
    Never never never ever use public properties in PHP. Ever. The only property in PHP that should be public is the use of const instead. It is datatype weak, you don't have an option but to enforce your datatypes at a set level.

    I wouldn't use magic functions either. I'd write accessors and mutators instead. Since arrays and object types can be typehinted (and if you write proper error handlers, so can primitives, but IMO that's a complete waste of processing), then it makes more sense to use a setter than a __set. __set will also become increasingly complicated to debug as more properties and rules are added.

    This is what I'd do:
    PHP Code:
        public function setPrice($price)
        {
            
    //enforce price into charge class
            
    if(! $price instanceof Charge$price = new Charge($price);
            
    $this->price $price;
        } 
    Although, if you allow one numerical and one object type entry, I'd probably reflect it as such:
    PHP Code:
    public function setPrice($dPrice)
    {
        if (!
    is_numeric($dPrice))
        {
            throw new 
    InvalidArgumentException("Price must be a double!");
        }
        
    $this->setPriceCharge(new Charge($dPrice));
    }

    public function 
    setPriceCharge(Charge $c)
    {
        
    $this->price $c;

    And with __set, I do this in my base object class:
    PHP Code:
    public function __set($var$val)
    {
        throw new 
    RuntimeException("Cannot set to property " $var);

    Automatic property creation in PHP is one of the stupidest thing's I've ever seen.

    If you *really* want property set ability, chain it from a set into a setter instead. Its much cleaner to look at. Assuming that you prepend all property names to a setter prefixed with set, then you can use reflection as well to determine the method to call.

  • Users who have thanked Fou-Lu for this post:

    hessodreamy (08-20-2012)

  • #3
    New Coder
    Join Date
    May 2005
    Location
    Leeds, UK
    Posts
    83
    Thanks
    1
    Thanked 0 Times in 0 Posts
    Thanks for the response. So what you're saying is: Don't be so lazy. write some proper getters & setters? Good plan.

    Quote Originally Posted by Fou-Lu View Post
    If you *really* want property set ability, chain it from a set into a setter instead. Its much cleaner to look at. Assuming that you prepend all property names to a setter prefixed with set, then you can use reflection as well to determine the method to call.
    Can you explain what you mean by this?

  • #4
    God Emperor Fou-Lu's Avatar
    Join Date
    Sep 2002
    Location
    Saskatoon, Saskatchewan
    Posts
    16,978
    Thanks
    4
    Thanked 2,659 Times in 2,628 Posts
    The easiest way is by use of a standard naming convention and reflection.
    PHP Code:
        public function __set($member$value)
        {
            try
            {
                
    $sFunc 'set' $member;
                
    $rf = new ReflectionMethod($this$sFunc);
                
    $rf->invoke($this$value);
            }
            catch (
    ReflectionException $ex)
            {
                throw 
    $ex;
            }
        } 
    And if you used it as such:
    PHP Code:
    <?php

    class Num
    {
        private 
    $num;

        public function 
    getNum()
        {
            return 
    $this->num;
        }

        public function 
    setNum($num)
        {
            if (!
    is_numeric($num))
            {
                throw new 
    InvalidArgumentException('Provided argument is not a number');
            }
            
    $this->num $num;
        }

        public function 
    __set($member$value)
        {
            try
            {
                
    $sFunc 'set' $member;
                
    $rf = new ReflectionMethod($this$sFunc);
                
    $rf->invoke($this$value);
            }
            catch (
    ReflectionException $ex)
            {
                throw 
    $ex;
            }
        }
    }

    $n = new Num();
    print_r($n);
    $n->setNum(5);
    print_r($n);
    $n->num 10;
    print_r($n);
    $n->num 'cat';
    print_r($n);
    You'd have a result of:
    Code:
    Num Object
    (
        [num:private] => 
    )
    Num Object
    (
        [num:private] => 5
    )
    Num Object
    (
        [num:private] => 10
    )
    
    Fatal error: Uncaught exception 'InvalidArgumentException' with message 'Provided argument is not a number' in /t.php:16
    Stack trace:
    #0 [internal function]: Num->setNum('cat')
    #1 /t.php(27): ReflectionMethod->invoke(Object(Num), 'cat')
    #2 /t.php(42): Num->__set('num', 'cat')
    #3 {main}
      thrown in /t.php on line 16


  •  

    Posting Permissions

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