Custom Getters On Activerecord

Hey!

Came into another problem:

I’m writing framework-like application for my school project, and even it’s optional class I need to have the code as clean as possible.

So the situation is, I have some framework component (let’s say you’re building a game on my fw, so it may be “Unit”), that defaultly provides data from database as a standard active record. But user of this fw has to have the possibility to replace this class with his custom class, let’s say “myUnit”.

For the fw to work, I need to force having specific attributes in this class, like "attack". My standard implementation has this attribute automatcilly loaded by ActiveRecord class from database attribute, so I can read it by




$model->attack;



The problem is, I have interface "IUnit", and the way interface should work is, it should declare method "getAttack()", so I could read it by




$model->getAttack();



What is the best way to achieve that? Is it ok if I just manually write all the getters into my activerecord class, like




public function getAttack(){

      return $this->attack;

}



It’s kinda sad that Yii doesn’t support those getters on db properties automatically.

I don’t have any experience with Yii so I’d be glad if someone who does would just tell me that my way is correct and that it’s not gonna cause any problems to Yii, or suggest me some better way to achieve this behaviour.

Note: I know I could just declare “getAttribute()” in the interface, but I think my teacher wouldn’t be very happy about that cause having only this in my interface doesn’t really forces custom methods to have the properties needed (like “attack”).

Thanks.

Actually, it does :)

http://www.yiiframework.com/wiki/167/understanding-virtual-attributes-and-get-set-methods/

Actually Yii AR does not expose "attribute". What it does is more "magic". It keeps all row fields in private array and implements __get(), __set() and __isset() PHP methods. Those methods are automatically called if they are defined and user wants to access object attribute that does not exist. There is also logic that disallows accessing fields that were not declared.

So:

$object->attack (if attack property does not exists) is converted to:

$object->__get( ‘attack’ ); and implementation of this method checks if there is ‘attack’ field in database table and then it returns $this->getAttribute( ‘attack’ );

so - even if there are not explicit getters and setters for every attribute - they are quite well simulated by those magic-methods and objects keep integrity (you cannot for example easily add new properties like in standard PHP objects with just $object->newProperty = XXX;)

I’ve read this article before, but I guess I didn’t fully understand though :) also I meant I was more sad that I can’t use the explicit getters automatically on db attributes (like not writing all the explicit getters while using them), but I guess I would want too much magic then :) (also now comes to my mind that it would probably raise PhP error anyway because of not implementing the methods of the interface, so nevermind, my bad, sorry for bothering & thanks for the info).

Thanks for the clarifying and letting me view how it works inside better!

So I should basically stick to my original idea. I was just making sure that having explicit getter with the name of a db attribute is not gonna mess up this “call chain”. (also I’m lazy to write all the getters for my activerecord class but now it’s clear to me that it’s unavoidable)

Looking back at it, my question seems pretty stupid even to me, I’ll try to think more before I’m gonna post something next time.

And just to be clear and maybe for someone in the future, here’s what I’m gonna do:

IUnit:




interface IUnit{

   public function getAttack();

...

}



Unit (default)




class Unit extends CActiveRecord implements IUnit {

    public function gettAtack(){

       return $this->attack; // returns value from database

    }

...

}



Any other custom unit:




class MyUnit implements IUnit {


   define CONST_ATTACK = 10;


    public function gettAtack(){

       return CONST_ATTACK; // for example, returns constant or whatever

    }

...

}



Hmm ok one more question though:

If I’ll write also custom setters, in a really simple way like:




class Unit extends CActiveRecord implements IUnit{

   public function setAttack($val){

      $this->attack = $val;

   }

}



will all the validation rules still be called in a standard way?

Also, how would following thing work?




$model = new Unit;

$model->setAttributes($attr);



Is this gonna call the explicit setters (on db attributes)? Will the setAttack() be called in this case, or is it just gonna use standard “magic” (__set()) method? (or you know, if I’m just gonna use standard Yii-way to fill the model from a form?)

Ok, if anyone comes across the same problem in the future: setAttributes is not gonna call explicit setters, however it’s possible to customize your MyActiveRecord class like this:




<?php


/**

 * Class with customization of CActiveRecord class of Yii framework

 */

class MActiveRecord extends CActiveRecord {


    public function setAttributes($values, $safeOnly = true) {

        if (!is_array($values))

            return;

        $attributes = array_flip($safeOnly ? $this->getSafeAttributeNames() : $this->attributeNames());

        foreach ($values as $name => $value) {

            $nameRest = substr($name, 1);

            $func = 'set' . strtoupper($name[0]) . $nameRest;

            if (method_exists($this, $func)) {

                $this->$func($value);

            } else if (isset($attributes[$name]))

                $this->$name = $value;

            else if ($safeOnly)

                $this->onUnsafeAttribute($name, $value);

        }

    }


}


?>



Of course you still have to write all setters and getters manually in order to use interfaces (on multiple class with different inheritance), however this piece of code will at least allow you to still use




$model->attributes = array(...);



instead of calling the setters explicitly for each property.