Automatic properties

Hi!

I want my AR to have automatically generated properties.

I'll draw an example. Imagine we have an AR 'User'. User has properties 'firstName' and 'lastName'. In some cases (not always) I want my User instance to have third attribute 'fullName' that would be a concatenation of 'firstName' and 'lastName'.

How do I do it properly?


<?php


class User extends arbase

{

   ...


   public function getFullName()

   {

      if(this is an inappropriate case)

         return null;

      return $this->firstName.' '.$this->lastName;

   }

   ...

}


echo $user->fullName;

Note that if you want not only read but write fullName, you have to set up a setter method as well.


<?php


public function setFullName($full)

{

   if(inappropriate case)

      return;

   list($this->firstName, $this->lastName) = explode(' ', $full);

}


$user->fullName='Bill Gates'; // both first and last name are in place by now.

saying “use getters and setters” would be enough :)

But this way doesn't fit into my application structure.

I thought about using scopes. I tried something like this:



<?php


class User extends CActiveRecord


{


	public function scopes()


	{


		return array(


			'witFullName'=>array(


				'select'=>'concat(firstName, " ", lastName) as fullName',


			),


		);


	}


}


But it doesn't work.

If you don’t store the full name, why do you concatenate it in the db layer?

What structure requires different solution?

scope is not the ideal solution to this problem. getter/setter is better.

The main reason why I don't want to use getters is that I want to be able to throw a list of users over RemoteObject interface into flex frontend. I.e. I want to be able to do something like:



<?php


return User::model()->withFullName()->findAll();


The whole list will fly into my flex application that will just display FullNames of users instead of doing concatenation itself.

And as far as I understand getters would not fit into picture.

Why not? I haven’t used Flex so far, but to external usage, it’d make no difference whether it calls model->firstName or fullName, as both are handled via getter magic method.

My extension (Pogostick Yii Extensions) adds this capability. Extend my CPSComponent and use the addOption() method to add propertiexs. They can then be retrieved like obj->propName.

The object comes into flex in serialized way. In flex it appears as a Flex Object similar to JavaScript dictionary: {'firstName':'Angelina', 'lastName':'Jolie'}. Object methods are not available.

Only standard attributes are available. It is so because they are in $user->attributes dictionary i guess. So since my new attribute exists only as getter method it will not be available in flex.

What about defining [font="Courier New"]public $fullName[/font], and determining its value in the afterFind() event?

I already thought about using afterFind. It's not that good either. AfterFind triggers every time find() is called. I need my new attributes to appear only in some case (e.g. upon transfer into flex). Of course I can try to limit afterFind() functionality by If statement. But it's really ugly.

In the end you may end up with a more uglier code.

What about combining afterFind and scenario?

Quote

My extension (Pogostick Yii Extensions) adds this capability. Extend my CPSComponent and use the addOption() method to add propertiexs. They can then be retrieved like obj->propName.

I looked at your extension. It looks solid. But I think it's not efficient to use yet another framework just to add some minor functionality…

Quote

In the end you may end up with a more uglier code.

What about combining afterFind and scenario?

I thought about scenario as well :)

As far as I understand scenario can be applied to particular instance. So if

$user = User::model()->find();

then I can apply a scenario to it:

$user->setScenario('withFullName');

But how do I apply scenario to group operation:

$users = User::model()->findAll();

foreach($users). :)

I don’t exactly know why you don’t simply ignore this additional functionality in cases when you don’t need fullName.

I guess I went in wrong direction. I solved my problem. Though not as beautiful as it could be. But it's efficient. I've made a class with static functions



<?php


class MyLib {





	public static function userForComboBox($user)


	{


		return array('label'=>$user->firstName.' '.$user->lastName, 'data'=>$user->id);


	}


}


Then I just call array_map() over my list of users:



<?php


array_map(array('MyLib', 'userForComboBox'), User::model()->findAll());





I don't like it but I can't think of anything else.