Because an extension is meant to be used by third-party developers, it takes some additional efforts to create it. The followings are some general guidelines:
In the following, we describe how to create a new extension, according to its categorization as described in overview. These descriptions also apply when you are creating a component mainly used in your own projects.
An application component should implement the interface IApplicationComponent or extend from CApplicationComponent. The main method needed to be implemented is IApplicationComponent::init in which the component performs some initialization work. This method is invoked after the component is created and the initial property values (specified in application configuration) are applied.
By default, an application component is created and initialized only when it is accessed for the first time during request handling. If an application component needs to be created right after the application instance is created, it should require the user to list its ID in the CApplication::preload property.
To create a behavior, one must implement the IBehavior interface. For convenience, Yii provides a base class CBehavior that already implements this interface and provides some additional convenient methods. Child classes mainly need to implement the extra methods that they intend to make available to the components being attached to.
When developing behaviors for CModel and CActiveRecord, one can also extend CModelBehavior and CActiveRecordBehavior, respectively. These base classes offer additional features that are specifically made for CModel and CActiveRecord. For example, the CActiveRecordBehavior class implements a set of methods to respond to the life cycle events raised in an ActiveRecord object. A child class can thus override these methods to put in customized code which will participate in the AR life cycles.
The following code shows an example of an ActiveRecord behavior. When this behavior is
attached to an AR object and when the AR object is being saved by calling save()
, it will
automatically sets the create_time
and update_time
attributes with the current timestamp.
class TimestampBehavior extends CActiveRecordBehavior
{
public function beforeSave($event)
{
if($this->owner->isNewRecord)
$this->owner->create_time=time();
else
$this->owner->update_time=time();
}
}
A widget should extend from CWidget or its child classes.
The easiest way of creating a new widget is extending an existing widget and overriding its methods or changing its default property values. For example, if you want to use a nicer CSS style for CTabView, you could configure its CTabView::cssFile property when using the widget. You can also extend CTabView as follows so that you no longer need to configure the property when using the widget.
class MyTabView extends CTabView
{
public function init()
{
if($this->cssFile===null)
{
$file=dirname(__FILE__).DIRECTORY_SEPARATOR.'tabview.css';
$this->cssFile=Yii::app()->getAssetManager()->publish($file);
}
parent::init();
}
}
In the above, we override the CWidget::init method and assign to
CTabView::cssFile the URL to our new default CSS style if the property
is not set. We put the new CSS style file under the same directory
containing the MyTabView
class file so that they can be packaged as
an extension. Because the CSS style file is not Web accessible, we need
to publish as an asset.
To create a new widget from scratch, we mainly need to implement two methods:
CWidget::init and CWidget::run. The first method is called when we
use $this->beginWidget
to insert a widget in a view, and the
second method is called when we call $this->endWidget
.
If we want to capture and process the content displayed between these two
method invocations, we can start output buffering
in CWidget::init and retrieve the buffered output in CWidget::run
for further processing.
A widget often involves including CSS, JavaScript or other resource files in the page that uses the widget. We call these files assets because they stay together with the widget class file and are usually not accessible by Web users. In order to make these files Web accessible, we need to publish them using CWebApplication::assetManager, as shown in the above code snippet. Besides, if we want to include a CSS or JavaScript file in the current page, we need to register it using CClientScript:
class MyWidget extends CWidget
{
protected function registerClientScript()
{
// ...publish CSS or JavaScript file here...
$cs=Yii::app()->clientScript;
$cs->registerCssFile($cssFile);
$cs->registerScriptFile($jsFile);
}
}
A widget may also have its own view files. If so, create a directory named
views
under the directory containing the widget class file, and put all the
view files there. In the widget class, in order to render a widget view, use
$this->render('ViewName')
, which is similar to what we do in a controller.
An action should extend from CAction or its child classes. The main method that needs to be implemented for an action is IAction::run.
A filter should extend from CFilter or its child classes. The main methods that need to be implemented for a filter are CFilter::preFilter and CFilter::postFilter. The former is invoked before the action is executed while the latter after.
class MyFilter extends CFilter
{
protected function preFilter($filterChain)
{
// logic being applied before the action is executed
return true; // false if the action should not be executed
}
protected function postFilter($filterChain)
{
// logic being applied after the action is executed
}
}
The parameter $filterChain
is of type CFilterChain which contains information
about the action that is currently filtered.
A controller distributed as an extension
should extend from CExtController, instead of CController. The main reason
is because CController assumes the controller view files are located under
application.views.ControllerID
, while CExtController assumes the view
files are located under the views
directory which is a subdirectory of
the directory containing the controller class file. Therefore, it is easier
to redistribute the controller since its view files are staying together
with the controller class file.
A validator should extend from CValidator and implement its CValidator::validateAttribute method.
class MyValidator extends CValidator
{
protected function validateAttribute($model,$attribute)
{
$value=$model->$attribute;
if($value has error)
$model->addError($attribute,$errorMessage);
}
}
A console command should extend from CConsoleCommand and implement its CConsoleCommand::run method. Optionally, we can override CConsoleCommand::getHelp to provide some nice help information about the command.
class MyCommand extends CConsoleCommand
{
public function run($args)
{
// $args gives an array of the command-line arguments for this command
}
public function getHelp()
{
return 'Usage: how to use this command';
}
}
Please refer to the section about modules on how to create a module.
A general guideline for developing a module is that it should be self-contained. Resource files (such as CSS, JavaScript, images) that are used by a module should be distributed together with the module. And the module should publish them so that they can be Web-accessible.
Developing a generic component extension is like writing a class. Again, the component should also be self-contained so that it can be easily used by other developers.
Found a typo or you think this page needs improvement?
Edit it on github !
Widgets & themes support
In widgets better to use $file=$this->getViewPath().DIRECTORY_SEPARATOR.'tabview.css' instead of $file=dirname(__FILE__).DIRECTORY_SEPARATOR.'tabview.css'; to provide support of themes.
Theme View Path Support.
<?php /** * Class GCWidget extends CWidget, * * This is a Wrapper for the YII's CWidget Class to support Widget's View Path. * @author Bijay Rungta * */ class GCWidget extends CWidget { /** * Returns the directory containing the view files for this widget. * The default implementation returns the 'views' subdirectory of the directory containing the widget class file. * @return string the directory containing the view files for this widget. */ public function getThemeViewPath() { $className = get_class($this); if (!empty(Yii::app()->theme)) { return Yii::app()->theme->basePath . DIRECTORY_SEPARATOR . 'views' . DIRECTORY_SEPARATOR . get_class($this); } return null; } /** * Looks for the view script file according to the view name. * This method will look for the view under the widget's {@link getViewPath viewPath}. * The view script file is named as "ViewName.php". A localized view file * may be returned if internationalization is needed. See {@link CApplication::findLocalizedFile} * for more details. * Since version 1.0.2, the view name can also refer to a path alias * if it contains dot characters. * @param string name of the view (without file extension) * @return string the view file path. False if the view file does not exist * @see CApplication::findLocalizedFile */ public function getViewFile($viewName) { if (($renderer = Yii::app()->getViewRenderer()) !== null) { $extension = $renderer->fileExtension; } else { $extension = '.php'; } if (strpos($viewName, '.')) { // a path alias $viewFile = Yii::getPathOfAlias($viewName); } else { if ($themeViewPath = $this->getThemeViewPath()) { $viewFile = $themeViewPath . DIRECTORY_SEPARATOR . $viewName; if (is_file($viewFile . $extension)) { return Yii::app()->findLocalizedFile($viewFile.$extension); } elseif ($extension !== '.php' && is_file($viewFile . '.php')) { return Yii::app()->findLocalizedFile($viewFile . '.php'); } } $viewFile = $this->getViewPath() . DIRECTORY_SEPARATOR . $viewName; } if (is_file($viewFile . $extension)) { return Yii::app()->findLocalizedFile($viewFile.$extension); } elseif ($extension !== '.php' && is_file($viewFile . '.php')) { return Yii::app()->findLocalizedFile($viewFile . '.php'); } else { return false; } } }
Signup or Login in order to comment.