Class Level Methods vs Static Methods

It seems that many developers are a little confused with the use of what in Yii is referred to as Class Level Methods and don’t understand how to use them as opposed to Static Methods. For example people write Comments::generateTags() instead of Comments::model()->generateTags(). This is understandable, given how little guidance is given on this… Here’s the more detailed guidance. What Class Level Methods are are effectively Static Methods BUT with the benefit of being able to use inheritance. That is, you can override the operation of a Class Level Method in a subclass, whereas if you used a static method you would not be able to override it. Much of the confusion actually comes from the fact that you’re putting something that acts on the recordset (as opposed to on an individual record) as a non-static method of an active record class and it just feels weird, so you drop back to using a static method. Usually, what one would do is if they wanted to create methods to act on the recordset rather than the individual record (and have the benefit of inheritance) is to create a Manager/Module class which is responsible for operations on sets of records. For example CommentManager/CommentModule (note that module is not used in the sense that Yii uses it but in the sense that Martin Fowler uses it when describing Table Module pattern). As Martin Fowler points out on on p. 127 of his book called Patterns of Enterprise Application Architecture, the benefit of an instance is inheritance. Effectively what the designers of Yii have done is rolled this all into the one ActiveRecord class. So it might feel a little weird at first, but then you get used to it. So, in general, you should use class level methods, not static methods, as it gives you the benefit of inheritance although it might feel a little weird. Then you call them using $class::model()->method(). Hope this helps developers understand how to structure their code.

I’ve also added this info to the Difinitive Guide to Yii on the page that explains AR.

Very nice explanation! Thanks.

In general,using $class::model()->method() would benefit from inheritance although it might feel a little weird.

refer to the source code


public static function model($className=__CLASS__)

{

    if(isset(self::$_models[$className]))

        return self::$_models[$className];

    else

    {

        $model=self::$_models[$className]=new $className(null);

        $model->_md=new CActiveRecordMetaData($model);

        $model->attachBehaviors($model->behaviors());

        return $model;

    }

}

who can analyse this further.

Since I’ve started using Yii I’ve always accepted this explanation but today I’m documenting some of my code and I wanted to be sure I had the wording just right on what exactly this method does. I began by looking this post up again and the phrase, “…you can override the operation of a Class Level Method in a subclass, whereas if you used a static method you would not be able to override it.” kept getting stuck in my head because I was sure that was not the case. It may have once been the case but it is no longer true. To ensure that I was not incorrect, I’ve made a test file to show that this is indeed not the case:


<?php

define('BUBBLE', true);


class BaseClass {

	/** This was taken from Yii CActiveRecord class (with the irrelevant parts removed for simplicity). */

	public static function model($className=__CLASS__) {

		$model=new $className(null);

		return $model;

	}


	/** This just formats the output a little nicer. */

	public static function rpt($msg, $this_exists) { echo "<p class=fun>$msg() called; \$this <b>does".($this_exists?'':' not')."</b> exist</p>\n"; }


	public static function staticTest() { BaseClass::rpt('BaseClass::staticTest', isset($this)); } 

	public function instanceTest() { BaseClass::rpt('BaseClass::instanceTest', isset($this)); } 

}


class MidClass extends BaseClass {

	public static function model($className=__CLASS__) { return parent::model($className); }


	public static function staticTest() { BaseClass::rpt('MidClass::staticTest', isset($this)); if(BUBBLE) parent::staticTest(); } 

	public function instanceTest() { BaseClass::rpt('MidClass::instanceTest', isset($this)); if(BUBBLE) parent::instanceTest(); } 

}


class UndefinedClass extends MidClass {

	public static function model($className=__CLASS__) { return parent::model($className); }

}


class FinalClass extends UndefinedClass {

	public static function model($className=__CLASS__) { return parent::model($className); }

	

	public static function staticTest() { BaseClass::rpt('FinalClass::staticTest', isset($this)); if(BUBBLE) parent::staticTest(); } 

	public function instanceTest() { BaseClass::rpt('FinalClass::instanceTest', isset($this)); if(BUBBLE) parent::instanceTest(); } 

}


class NewerClass extends FinalClass {

	public static function model($className=__CLASS__) { return parent::model($className); }

	

	public static function staticTest() { BaseClass::rpt('NewerClass::staticTest', isset($this)); if(BUBBLE) parent::staticTest(); } 

	public function instanceTest() { BaseClass::rpt('NewerClass::instanceTest', isset($this)); if(BUBBLE) parent::instanceTest(); } 

}


?><!DOCTYPE html>

<html lang=en-US>

<head>

	<meta charset=UTF-8>

	<title>Model Tests</title>

	<style>

		p.fun {padding-left:25px;}

	</style> 

</head>

<body>

	<h1>Model Tests</h1>

<?php


echo "<h3>Final Class -- Static Method Test without model()</h3>\n";

FinalClass::staticTest();


echo "<h3>Final Class -- Static Method Test with model()</h3>\n";

FinalClass::model()->staticTest();


$testClass=new FinalClass;

echo "<h3>Final Class -- Instance Method Test without model()</h3>\n";

$testClass->instanceTest();


echo "<h3>Final Class -- Instance Method Test with model()</h3>\n";

FinalClass::model()->instanceTest();


echo "<h3>Undefined Class -- Static Method Test without model()</h3>\n";

UndefinedClass::staticTest();


echo "<h3>Undefined Class -- Static Method Test with model()</h3>\n";

UndefinedClass::model()->staticTest();


$testClass=new UndefinedClass;

echo "<h3>Undefined Class -- Instance Method Test without model()</h3>\n";

$testClass->instanceTest();


echo "<h3>Undefined Class -- Instance Method Test with model()</h3>\n";

UndefinedClass::model()->instanceTest();


?>

If you were to play with this code you’d find out that no matter how you call these methods they ALL work exactly the same except that (as expected) the $this variable exists in only in the instance methods.

It is possible that providing a $this variable is the true purpose of this method but (as the OP has mentioned already) it shouldn’t be needed at all for the methods which use model() because those are methods which work on the dataset instead of working with an individual record.

Is there another reason that this method is needed? Has it been made obsolete by a PHP update (and is therefore only needed for compatibility)?

(BTW: I’m using PHP 5.4.9-4ubuntu2.2)

I see now, this method was implemented as a hack to work around the fact that at some point Late Static Binding had not yet been implemented. Apparently, if you use PHP 5.3+ you should be able to safely quit using these methods. One caveat would be that if you use a module or extension which uses this you may run into problems.