Two questions about MVC best pratices

Hi there,

There is a very good reading about MVC Best Practices added to current release of Yii and I would like to ask two questions extending this subject.

  1. Where to keep code for retrieving model data passed to layout instead of view? Can’t do this in controller (as for view) because current layout might be used by many controllers. Example: I have page footer defined in main.php layout file and this footer contains links list built by basing on Pages model. Where to keep code generating links? In model? Directly in a view (doesn’t seems to be a good idea)?

  2. If I define additional field do my model (AR) that is not stored in DB, only calculated basing on other fields, where to keep code that will calculate this additional field value? I thought that in a model in method called by AR directly after find (to fill missing field not retrieved by find itself), but found only OnBeforeFind method declared. Where then? In a controller - extending default implementation of loadModel method and calculating additional field value right after model is successfully retrieved?

Cheers,

Trejder

  1. protected/components/Controller.php

You can add a public property. Then you can set its value / add things to array / … from every view. Later in the layout you can access these values. Or add a custom method that somehow generates what you need in the layout. Or use getter/setter methods (see below) - there are many ways to do it.

  1. Use a getter with a private "cache" variable



private $_someVal;

public function getSomeVal()

{

    if ($this->_someVal===null)

        $this->_someVal= 'result of complex operation, using '.$this->name;


    return $this->_someVal;

}

I quote Max, but I consider that the first solution is more "squared", and is also the one implemented in the default application.

In response to part 2, there were two different ways I’ve stored extra properties that do not exist in the database. Both ways utilize the onAfterFind event. At first I was just defining the properties in the AR model itself




public $newProp;


/**

 * initialize the class and set the after find event

 */

public function init() {

		$this->onAfterFind=array($this,'setProps');

}

	

/**

 * Set the new property based on other properties

 */

public function setProps($event) {

		$this->newProp = $this->dbProp1 . ' '. $this->dbProp2;

}



You can also put the setProps function in it’s own event class file




public function init() {

		$this->onAfterFind=array(new SetPropsEvent,'setProps');

}



More recently, I started putting it in behaviors by extending CActiveRecordBehavior




class ActiveRecordBehavior extends CActiveRecordBehavior

{

	private $newProp = '';

	

	public function getNewProp() {return $this->newProp;}

	

	public function afterFind($event)

	{

		//set new properties

		$this->newProp = $event->sender->dbProp1 . ' '. $event->sender->dbProp2;

	}

}



and then adding the behavior to the AR model




/**

 * Returns a list of behaviors that this model should behave as.

 * @return array of behavior configurations indexed by behavior names

 */


public function behaviors() {

		return array(

			'prop'=>array(

				'class'=>'application.behaviors.ActiveRecordBehavior',

			),

		);

}



which you can access using asa




$model->asa('prop')->newProp;



Hope this helps!

I know. I was rather asking, what would be most closely fulfilling MVC guidelines? Is declaring a custom method in a model and then calling it from within layout one of solution that can be accepted as OK in MVC?

What is the advantage of using your caching idea? I mean - why can’t I do this like this:




public function getSomeVal()

{

	return  'result of complex operation, using '.$this->name;

}

is this because your code assures that DB is queried only once per request instead of doing this each time?

Just my curiosity, because I’m bit tired and maybe I’m missing something obvious.

Why do you use such approach

Instead of simply declaring onAfterFind that would overload base one?

That would work too and would be shorter than the way I was doing it. It all depends on how you want to organize your code.

I just realized that you don’t have to use the asa method to access a property of a behavior that has been attached to a component. The behaviors properties and methods can both be accessed through the component implementation thanks to the __set magic method. Nice magic feature!

You’re even encouraged to add custom methods to a model - that’s the way how you keep your business logic where it belongs: In the model section. And remember: If you define a getter, you add (what i call) a “virtual” attribute to the model. In your layout you won’t be able to tell the difference:


<?php echo $model->someVal; ?>

If you add a setter, you can even make it writable and use it e.g. in forms like a regular attribute. Getter/Setter are a very powerful OO concept that can help you out of many problems.

It’s a “cache” for multiple accesses during 1 request. To not have to re do the complex operation every time you access this value. It of course depends on how complex/expensive your calculation is. In the simple example given, you probably would not use that private cache variable. Also check the Yii sources, you’ll find this approach many times.

Just a note: You should override afterFind(), not onAfterFind().

I was aware of that. But, since I’m very beginner to these things I wanted to ask just one last question, to have full picture of this - is it Yii feature (getter/setter method for non-existing property/variables) or pure PHP5 / OOP feautre. But it seems you’ve already answered me that the second one is correct answer.

Sorry - this is the effect of writing on the forum after just four hours of sleep! :] I know that this is enough for Bill Gates and that thanks to this you built his whole empire (right! :]), but it is certainly not enough for me! :]

Yii adds the nice magic __get()/__set() methods in CComponent. That’s why mostly all other components extend from that - so they all share this functionality.

I’d suggest to study that code in base/CComponent.php - you’ll find that it scans the current class for get*()/set*() methods.

Yeap, it is right here:

[url=&quot;http://code.google.com/p/yii/source/browse/tags/1.1.6/framework/base/CComponent.php#108&quot;]

public function __get($name)[/url]

public function __set($name,$value)

and in the comment opening this file (CComponent.php):

and it really is very useful, especially in the problems like I wrote. Thanks again.