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,197