Version 3 end of life
This version of Silverstripe CMS will not recieve any additional bug fixes or documentation updates. Go to documentation for the most recent stable version.

Extensions and DataExtensions

An Extension allows for adding additional functionality to a Object or modifying existing functionality without the hassle of creating a subclass. Developers can add Extensions to any Object subclass within core, modules or even their own code to make it more reusable.

Extensions are defined as subclasses of either DataExtension for extending a DataObject subclass or the Extension class for non DataObject subclasses (such as Controllers)

[info] For performance reasons a few classes are excluded from receiving extensions, including Object, ViewableData and RequestHandler. You can still apply extensions to descendants of these classes. [/info]

mysite/code/extensions/MyMemberExtension.php

	<?php
	
	class MyMemberExtension extends DataExtension {

		private static $db = array(
			'DateOfBirth' => 'SS_Datetime'
		);

		public function SayHi() {
			// $this->owner refers to the original instance. In this case a `Member`.
			return "Hi " . $this->owner->Name;
		}
	}

Convention is for extension class names to end in Extension. This isn't a requirement but makes it clearer [/info]

After this class has been created, it does not yet apply it to any object. We need to tell SilverStripe what classes we want to add the MyMemberExtension too. To activate this extension, add the following via the Configuration API.

mysite/_config/app.yml

	Member:
	  extensions:
	    - MyMemberExtension
	Member::add_extension('MyMemberExtension');

transformed the original Member class in two ways:

  • Added a new SS_Datetime for the users date of birth, and;
  • Added a SayHi method to output Hi <User>

From within the extension we can add more functions, database fields, relations or other properties and have them added to the underlying DataObject just as if they were added to the original Member class but without the need to edit that file directly.

Adding Database Fields

Extra database fields can be added with a extension in the same manner as if they were placed on the DataObject class they're applied to. These will be added to the table of the base object - the extension will actually edit the $db, $has_one etc.

mysite/code/extensions/MyMemberExtension.php

	<?php

	class MyMemberExtension extends DataExtension {

		private static $db = array(
			'Position' => 'Varchar',
		);

		private static $has_one = array(
			'Image' => 'Image',
		);

		public function SayHi() {
			// $this->owner refers to the original instance. In this case a `Member`.
			return "Hi " . $this->owner->Name;
		}
	}
	$CurrentMember.Position
	$CurrentMember.Image

Adding Methods

Methods that have a unique name will be called as part of the __call method on Object. In the previous example we added a SayHi method which is unique to our extension.

mysite/templates/Page.ss

	<p>$CurrentMember.SayHi</p>

	// "Hi Sam"
	$member = Member::currentUser();
	echo $member->SayHi;

	// "Hi Sam"

Modifying Existing Methods

If the Extension needs to modify an existing method it's a little trickier. It requires that the method you want to customise has provided an Extension Hook in the place where you want to modify the data. An Extension Hook is done through the Object::extend() method.

framework/security/Member.php

	public function getValidator() {
		// ..
		
		$this->extend('updateValidator', $validator);

		// ..
	}

variables at that given point. In this case, the core function getValidator on the Member class provides an updateValidator hook for developers to modify the core method. The MyMemberExtension would modify the core member's validator by defining the updateValidator method.

mysite/code/extensions/MyMemberExtension.php

	<?php

	class MyMemberExtension extends DataExtension {

		// ..

		public function updateValidator($validator) {
			// we want to make date of birth required for each member
			$validator->addRequiredField('DateOfBirth');
		}
	}

The $validator parameter is passed by reference, as it is an object. [/info]

Another common example of when you will want to modify a method is to update the default CMS fields for an object in an extension. The CMS provides a updateCMSFields Extension Hook to tie into.

	<?php

	class MyMemberExtension extends DataExtension {

		private static $db = array(
			'Position' => 'Varchar',
		);

		private static $has_one = array(
			'Image' => 'Image',
		);

		public function updateCMSFields(FieldList $fields) {
	  		$fields->push(new TextField('Position'));
	  		$fields->push(new UploadField('Image', 'Profile Image'));
		}
	}

[notice] If you're providing a module or working on code that may need to be extended by other code, it should provide a hook which allows an Extension to modify the results. [/notice]

	public function Foo() {
		$foo = // ..

		$this->extend('updateFoo', $foo);

		return $foo;
	}

you need to provide extension hooks at the beginning of the method use before{..}.

Owner

In your Extension class you can only refer to the source object through the owner property on the class as $this will refer to your Extension instance.

	<?php

	class MyMemberExtension extends DataExtension {

		public function updateFoo($foo) {
			// outputs the original class
			var_dump($this->owner);
		}
	}

To see what extensions are currently enabled on an object, use Object::getExtensionInstances() and Object::hasExtension()

	$member = Member::currentUser();

	print_r($member->getExtensionInstances());
	
	if($member->hasExtension('MyCustomMemberExtension')) {
		// ..
	}

Object extension injection points

Object has two additional methods, beforeExtending and afterExtending, each of which takes a method name and a callback to be executed immediately before and after Object::extend() is called on extensions.

This is useful in many cases where working with modules such as Translatable which operate on DataObject fields that must exist in the FieldList at the time that $this->extend('UpdateCMSFields') is called.

[notice] Please note that each callback is only ever called once, and then cleared, so multiple extensions to the same function require that a callback is registered each time, if necessary. [/notice]

Example: A class that wants to control default values during object initialization. The code needs to assign a value if not specified in self::$defaults, but before extensions have been called:

	function __construct() {
		$self = $this;

		$this->beforeExtending('populateDefaults', function() use ($self) {
			if(empty($self->MyField)) {
				$self->MyField = 'Value we want as a default if not specified in $defaults, but set before extensions';
			}
		});

		parent::__construct();
	}

[notice] This method is preferred to disabling, enabling, and calling field extensions manually. [/notice]

	public function getCMSFields() {

		$this->beforeUpdateCMSFields(function($fields) {
			// Include field which must be present when updateCMSFields is called on extensions
			$fields->addFieldToTab("Root.Main", new TextField('Detail', 'Details', null, 255));
		});

		$fields = parent::getCMSFields();
		// ... additional fields here
		return $fields;
	}

Related Documentaion

API Documentation