Pagetypeitis

Antipatterns and Solutions 
for SilverStripe's page types

The good

Page types are great building blocks
Support OOP AND MVC architectures
Great for beginners

The Bad

Hard to SCALE FOR complex PROJECTS
Tend to influence other areas

Impacts

Leaks into UI
Too many types, unclear purpose

Leaks into architecture
Template variations drive page types

Harder to mix'n'match interface components

Antipattern: The God Page Object


* actual Page.php 

Antipattern: Page God Objects

Shared functionality on lowest common class
Often includes template helpers
Not much better than globals

Antipattern: Deep Inheritance

Over-specialization
Often necessitated by monolithic module design

Antipattern: Deep Inheritance

class ArticleHolder extends BlogHolder {
	private static $has_one = array('Author' => 'Member');
}
class TaggedArticleHolder extends ArticleHolder {
	private static $many_many = array('Tags' => 'Tag');
} 

Antipattern: Monolithic Structure

Extend class just to add one feature
Prevents composition from different classes
Particularly bad for modules

Antipattern: Monolithic Structure

// in a module
class BlogEntry extends Page {
	public function getTagCloud() {
		// lots of useful code here
	}
}

// in your project code
class ArticlePage extends BlogEntry {
	public function getTagCloudSorted() {
		return $this->getTagCloud()->sort('Name');
	}
}

Solutions

Solution: Template Includes

Reuse across page types
Document context expectations

Composition over Inheritance

Single purpose objects
Encourages horizontal code reuse
Improves testability (no controller mocking)

Composition Over Inheritance

class Tag extends DataObject {
	private static $db = array('Label' => 'Varchar');
}

class Page extends SiteTree {
	private static $many_many = array(
		'Tags' => 'Tag'
	);
}

class Page_Controller extends ContentController {
	public function TagCloud() {
		return new TagCloud(Page::get()->relation('Tags'));
	}
} 

Composition Over Inheritance

class TagCloud extends ViewableData {
    
    protected $tags;
	
	public function __construct($tags) {
		$this->tags = $tags;
	}

	public function TagsByCount() {
		$results = new ArrayList();
		$namesToCount = $this->tags->dataQuery()->query()
			->setGroupBy('Name')
			->setSelect('Name', 'COUNT(*)')
			->setOrderBy('COUNT(*) DESC')
			->execute()
			->map();
		
		foreach($namesToCount as $name => $count) {
			$results->push(new ArrayData(array(
				'Name' => $name,
				'Frequency' => $freq
			)));
		}

		return $results;
	}

} 

Solution: Extensions

Reuse across classes
Add model schemas, properties and methods
Limitation: No method or property overrides
Relies on extension points (canEdit, updateCMSFields, ...)
PHP 5.4 has "traits" with similar features

Solution: Extensions

class Taggable extends DataExtension {
	private static $many_many = array('Tags' => 'Tags');
}

// In mysite/_config.yml
BlogEntry:
  extensions:
    - Taggable 

Solution: Polymorphic Base Classes

Interface and behaviour variations
Based on model data, context or config 
Keeps inheritance "flat"

Solution: Polymorphic Base Classes

class ArticlePage extends Page {
	
	private static $db = array(
    		'Type' => 'Enum("News Entry,Blog Entry")'	);
	
	public function getCMSFields() {
		$fields = parent::getCMSFields();
		if($this->Type == 'Blog Entry') {
			$fields->addFieldToTab(
                'Root.Main', 
                new TagField()
            );
		}
		return $fields;
	}
} 

Solution: View Helpers

General purpose commands without controller context
Package as reuseable, independent functionality
Auto-detected through PHP interface usage
But: Not really OOP, use sparingly

Solution: View Helpers

class InflectionViewHelper implements TemplateGlobalProvider {
	
	public function Pluralize($str) {
		return Inflector::pluralize($str);
	}
	
	public static function get_template_global_variables() {
		return array('Pluralize')
	}
} 

Title

Pagetypeitis - Antipatterns and Solutions for SilverStripe's page types

By chillu

Pagetypeitis - Antipatterns and Solutions for SilverStripe's page types

  • 1,079