SlideShare a Scribd company logo
1 of 60
Abstracting functionality with Centralised Content Michael Peacock
About me Senior Web Developer M.D. of design agency Peacock Carter Technical director for an online retailer Author www.michaelpeacock.co.uk me@michaelpeacock.co.uk @michaelpeacock
What's in store? Setting the scene – a look at the problem centralised content solves Centralised content – what is it and how can it solve this problem Implementation – How we implemented centralised content with PHP and MySQL
A sample bespoke CMS / basic e-commerce website Pages Blog entries Blog categories News articles Products Product Categories Events Users can buy products Users can rate products Users can comment on / review products Users can comment on blog entries
Commenting Needed for both blog entries and products Create a simple library or function to create a comment for us Comments table in our database Table to link them to blog entries Table to link them to products
Database
What about searching Do we have search boxes for different aspects of the site? Do we use a complex searching system? Should we just let Google do it for us? “Can’t someone else do it?” Homer J Simpson
What if in the future, once development is complete… Users need to be able to rate blog entries Users need to be able to purchase (book onto) events … and comment on them … … and rate them… Sounds like a pain!
Let’s take a step back… And centralise our content!
Centralised Content Useful architecture; especially for CMS projects MVC brings out the best in it Drupal “node” MVC would be nice, Drupal Used extensively in our own CMS and frameworks for the past year and a half;
Content Typical content found on a CMS powered site: Pages Blog entries News articles Job vacancies Products Events Photographs / Image gallery
Pages Name Heading Page content Meta data / title URL
Blog Entries Name Heading Blog entry Meta data / title Leading image Leading paragraph Author URL
News articles Name Heading News article Meta data / title Leading image Leading paragraph Author URL
Job Vacancies Name Heading Job description Location Application deadline Salary Meta data / title Author URL
Products Name Heading Product description Price Weight Image Meta data / title Author URL
Events Name Heading Event description Location Date Start / End time Meta data / title Author URL
Gallery Images Name Heading Image caption / description Camera data Location Image location Meta data / title Author URL
Its all the same! (well, almost…) Name Heading Title URL / Path / Search Engine friendly name Primary content / description / details Meta data Creator / Author  Active / Enabled Comments enabled / disabled
…with extra bits depending on the type Products Price; Stock level; SKU; Weight Events Venue; Spaces; Price; Date; Start time; End time Gallery image Image file location; Camera details; Location Job vacancy Salary; Location; Start date; Type; Application date
Content versions With content being centralised we can implement versioning more easily Record all versions of content Static content IDs which relate to the active version
So; let’s centralise it! Core fields will make up our main table of content (content_versions) Content types will have their own table, containing type specific fields content_versions_* A content table (content) will store static data, such as author, creation date, ID, and reference the current active version certain toggle-able fields (active, commentable) should go here Regardless of the version in play, the content ID can be used to access the element, and won’t change.
Core database
Within MVC Content model Models for each content type, extending the content model For administrative tasks (CMS) content controller to perform shared operations: toggle active, toggle comments, delete Content type controllers extend
Content model Deals exclusively with content and content versions tables Getters and setters for core content fields Creating content: Create new content version Create new content record, pointing to the version Editing content Create new content version Log the old version ID in a versions log Update the content record to point to the new version
Content: save public function save() {     // are we creating a new content item?     if( $this->id == 0 )     { /** create the content versions record */         $this->registry->getObject('db')->insertRecords( 'content_versions', $insert ); // record the ID         $this->revisionID = $this->registry->getObject('db')->lastInsertID(); /** insert the content record */         $this->registry->getObject('db')->insertRecords( 'content', $insert ); // record the ID         $this->id = $this->registry->getObject('db')->lastInsertID();     }     else     { // have we changed the revision, or just something from the content table?         if( $this->revisionChanged == true )         { // make a note of the old revision ID for the history             $this->oldRevisionID = $this->revisionID; /** insert the new content_versions record */             $this->registry->getObject('db')->insertRecords( 'content_versions', $insert ); // update the revisionID             $this->revisionID = $this->registry->getObject('db')->lastInsertID(); /** record the history */             $this->registry->getObject('db')->insertRecords( 'content_versions_history', $insert);         } /* update the content table */         $this->registry->getObject('db')->updateRecords('content', $update, 'ID=' . $this->id );     } }
Product (i.e. a content type) model Extends content model Getters and setters for extended data for the content type Creating product
Product: save public function save() { // Creating a new product     if( $this->getID() == 0 )     { parent::setType( $this->typeID );         parent::save();         $this->saveProduct();     }     else     {         // tells the parent, that the revision has changed,          // i.e. that we didn't just toggle active / change something from the _content_table! $this->setRevisionChanged( true );         parent::save();         $this->saveProduct();     } }
Product: Saving product data private function saveProduct() { /** insert product specific data */     // product version ID should be the same as the content version ID for 	easy maping     $insert['version_id'] = $this->getRevisionID();     $this->registry->getObject('db')->insertRecords( 	'content_versions_store_products', $insert );     // get the content ID     $pid = $this->getID();     // categories         // delete all associations with this product ID         // insert new ones based off user input; $pid to reference product     // shipping costs         // delete all associations with this ID         // insert new ones based off user input; $pid to reference product }
A load of CRUD! Creating New features / content types we only need to code for the extended fields Reading  Custom constructor in the child model Call setters for content type specific fields Call parent setters for core content Child method to iterate through / process fields to go to the template engine
A load of CRUD! Updating Parent deals with all the core fields (no new work!) Insert (versions) extended fields Use the ID the parent gives to the content version, to make table mapping easier Deleting Assuming “deleting” just hides content from front and back-end Parent object updates content record to deleted – no extra work!
Commenting Requires just one database table Can be used, without additional work, for all content types Each comment relates to a record in the content table
... commenting private function postComment( $id ) { require_once( FRAMEWORK_PATH . 'models/comment/comment.php');     $comment = new Commentmodel( $this->registry, 0 );     // tell the comment which content element it relates to     $comment->setContent( $id );     $comment->setName( isset( $_POST['comment_name'] ) ? $_POST['comment_name']: '' );     $comment->setEmail( isset( $_POST['comment_email'] ) ? $_POST['comment_email']: '' );     $comment->setURL( isset( $_POST['comment_url'] ) ? $_POST['comment_url']: '' );     $comment->setComment( isset( $_POST['comment_comment'] ) ? $_POST['comment_comment']: '' );     $comment->setIPAddress( $_SERVER['REMOTE_ADDR'] );     $comment->setCommentsAutoPublished( $this->registry->getSetting('blog.comments_approved') );     $comment->setPageURL( $this->registry->getURLBits() );     if( $comment->checkForErrors() )     {       /** error processing */     }     else     {         // save the post         $comment->save(); /** Redirect to the controller the user was on before, based on the URL */     } }
Rating Again, just one table Directly relates to the appropriate content element Create it once; works for all content types – no future work for new content types
Geo-tagging Either extend the content / versions tableOR Create a co-ordinates table and map it to the content table
What else? Keyword tagging Keywords table Content keywords associations table Central functionality to add keywords and delete orphaned keywords Categories A content type (up for debate) – why? New table to map content to content Central functionality to manage associations
Purchasing Provided content types have consistent cost / shipping fields, they can slot into the order pipeline Won’t work for all content types Makes conversion easy Make event purchasable? Make gallery image purchasable? Just add price fields, and indicate the content type can be purchased
Purchasing By giving an image a price field, pre-existing e-commerce functionality can process it Piece of cake
Hierarchies and ordering Ordering pages within the sites menu Moving pages within another page Ordering product categories Ordering blog entries Ordering news articles
Searching Search the content & content versions table LEFT JOIN content_versions_* tables where appropriate Designate searchable fields Execute the query Enjoy integrated search results!
Searching: Define our extended search fields private $extendedTablesAndFields = array(      'content_versions_news' => array( 'joinfield' => 'version_id', 'fields' => array( 'lead_paragraph' ) )  );
Searching: Build our left joins $joins = ""; $selects = ""; $wheres = " "; $priority = 4; $orders = ""; foreach( $this->extendedTablesAndFields as $table => $data ) {     $field = $data['joinfield'];     $joins .= " LEFT JOIN {$table} ON content.current_revision={$table}.{$field} ";     foreach( $data['fields'] as $field )     {         $wheres .= " IF( {$table}.{$field} LIKE '%{$phrase}%', 0, 1 ) <> 1 OR ";         $selects .= ", IF( {$table}.{$field} LIKE '%{$phrase}%', 0, 1 ) as priority{$priority} ";         $orders .= " priority{$priority} ASC, ";         $priority++;     } }
Searching: Query! $sql = "SELECT     *,      IF(content_types.reference='page',content.path,CONCAT(content_types.view_path,'/',content.path ) ) as access_path,      REPLACE(substr(content_versions.content,1,100),'<p>','') as snippet,      content_types.name as ct,      IF( content_versions.name LIKE '%{$phrase}%', 0, 1 ) as priority0,      IF( content_versions.title LIKE '%{$phrase}%', 0, 1 ) as priority1,      IF( content_versions.heading LIKE '%{$phrase}%', 0, 1 ) as priority2,      IF( content_versions.content LIKE '%{$phrase}%', 0, 1 ) as priority3  {$selects}  FROM      content      LEFT JOIN  content_types ON content.type=content_types.ID {$joins}      LEFT JOIN  content_versions ON content.current_revision=content_versions.ID  WHERE  content.active=1 AND  content.deleted=0 AND      ( IF( content_versions.name LIKE '%{$phrase}%', 0, 1 ) <> 1 OR          IF( content_versions.title LIKE '%{$phrase}%', 0, 1 ) <> 1 OR          IF( content_versions.heading LIKE '%{$phrase}%', 0, 1 ) <> 1 OR          IF( content_versions.content LIKE '%{$phrase}%', 0, 1 ) <> 1 OR {$wheres}      )  ORDER BY      priority0 ASC,      priority1 ASC,      priority2 ASC,      priority3 ASC,  {$orders} 1";
Searching: Results
Simple access permissions public function isAuthorised( $user ) { 	if( $this->requireAuthorisation == false ) 	{ 		return true; 	} elseif( $user->loggedIn == false ) 	{ 		return false; 	} elseif( count( array_intersect( $this->authorisedGroups, $user->groups ) ) > 0 ) 	{ 		return true; 	} 	else 	{ 		return false; 	} }
(Simple) Access permissions Single table mapping content to groups At content level, for content elements which require – cross reference the users groups with allowed groups
Downloads / Files / Resources Create them as a content type Searchable based off name / description Store the file outside of the web root Make use of access permissions already implemented Make them purchasable
Not just “content”! Other entities within an application which are similar Social Networks CRM’s
Social Networks: Statuses Core table: status Creator Profile status posted on (use it for both statuses and “wall posts” The status Creation date
Social Networks: Statuses Extended tables: Videos: YouTube Video URL Images: URL, Dimensions Links: URL, Title
... Build a stream Status streams Recent activity Viewing a profile’s “wall posts”
Query the stream Part of a stream model SELECT  t.type_reference, t.type_name, s.*,  p.name as poster_name, r.name as profile_name FROM  	statuses s, status_types t, profile p, profile r  WHERE  	t.ID=s.type AND p.user_id=s.poster AND r.user_id=s.profileAND  		( p.user_id={$user}  		OR r.user_id={$user}  		OR ( p.user_id IN ({$network}) AND r.user_id IN 			({$network}) )  		) ORDER BY  	s.ID DESC LIMIT {$offset}, 20
Generate the stream For each stream record Include a template related to: the stream item type, e.g. video The context, e.g. Posting on your own profile Insert stream record details into that instance of the template bit
foreach( $streamdata as $data ) {	 	if( $userUpdatingOwnStatus ) 	{ 		// updates to users "wall" by themselves 		$template->addBit( 'stream/types/' . $data['type_reference'] . '-me-2-me.tpl.php', $data ); 	} elseif( $statusesToMe ) 	{ 		// updates to users "wall" by someone 		$template->addBit( 'stream/types/' . $data['type_reference'] . '-2me.tpl.php', $datatags );	 	} elseif( $statusesFromMe ) 	{ 		// statuses by user on someone elses wall 		$template->addBit( 'stream/types/' . $data['type_reference'] . '-fromme.tpl.php', $datatags );	 	} 	else 	{ 		// friends posting on another friends wall 		$template->addBit( 'stream/types/' . $data['type_reference'] . '-f2f.tpl.php', $datatags );		 	} }
CRM’s People and organisations are very similar Centralise them! Entity Entity_Organisation Entity_Person Record Person <-> organisation relationships in a table mapping entity table onto itself
Extending: Adding new content types Drop in a model Getters and setters for extended fields Save method to insert extended data Drop in an administrator controller Rely on parent for standard operations Pass CRUD requests / data to the model Drop in a front end controller Viewing: over-ride parent method & extend the select query Anything else: Add specific functionality here
Why centralise your content / common entities? Eases content versioning Write functionality (commenting, rating, etc) once, and it works for all current and future content types – without the need for additional work Fixing bugs with abstracted features, fixes it for all aspects which use that feature Conversion is relatively easy – e.g. Turn a page into a blog entry.  More so if they don’t extend the core table
Some of the problems Can be a risk of content and content versions tables having too many fields Optimization bottle-neck – poorly performing and optimizing tables will cripple the entire site Versioning: Stores lots of data Sometimes you want functionality to differ across features (e.g. Product reviews <> comments) A bug in an abstract feature will be present in all aspects which use it
phpMyAdmin is harder Managing content via phpMyAdmin is more difficult MySQL Views can help with listing and viewing content
Thanks for listening www.michaelpeacock.co.uk me@michaelpeacock.co.uk @michaelpeacock  Please leave feedback!http://joind.in/2065

More Related Content

What's hot

cdac@parag.gajbhiye@test123
cdac@parag.gajbhiye@test123cdac@parag.gajbhiye@test123
cdac@parag.gajbhiye@test123
Parag Gajbhiye
 
How does get template part works in twenty ten theme
How does get template part works in twenty ten themeHow does get template part works in twenty ten theme
How does get template part works in twenty ten theme
mohd rozani abd ghani
 
Rails 3 overview
Rails 3 overviewRails 3 overview
Rails 3 overview
Yehuda Katz
 
Rails 3: Dashing to the Finish
Rails 3: Dashing to the FinishRails 3: Dashing to the Finish
Rails 3: Dashing to the Finish
Yehuda Katz
 
Class Intro / HTML Basics
Class Intro / HTML BasicsClass Intro / HTML Basics
Class Intro / HTML Basics
Shawn Calvert
 

What's hot (20)

cdac@parag.gajbhiye@test123
cdac@parag.gajbhiye@test123cdac@parag.gajbhiye@test123
cdac@parag.gajbhiye@test123
 
Polymer
PolymerPolymer
Polymer
 
Caldera Learn - LoopConf WP API + Angular FTW Workshop
Caldera Learn - LoopConf WP API + Angular FTW WorkshopCaldera Learn - LoopConf WP API + Angular FTW Workshop
Caldera Learn - LoopConf WP API + Angular FTW Workshop
 
Frontend for developers
Frontend for developersFrontend for developers
Frontend for developers
 
Assetic (Symfony Live Paris)
Assetic (Symfony Live Paris)Assetic (Symfony Live Paris)
Assetic (Symfony Live Paris)
 
How does get template part works in twenty ten theme
How does get template part works in twenty ten themeHow does get template part works in twenty ten theme
How does get template part works in twenty ten theme
 
Introducing Assetic: Asset Management for PHP 5.3
Introducing Assetic: Asset Management for PHP 5.3Introducing Assetic: Asset Management for PHP 5.3
Introducing Assetic: Asset Management for PHP 5.3
 
Rails 3 overview
Rails 3 overviewRails 3 overview
Rails 3 overview
 
Sightly - Part 2
Sightly - Part 2Sightly - Part 2
Sightly - Part 2
 
Rails 3: Dashing to the Finish
Rails 3: Dashing to the FinishRails 3: Dashing to the Finish
Rails 3: Dashing to the Finish
 
Connecting Content Silos: One CMS, Many Sites With The WordPress REST API
 Connecting Content Silos: One CMS, Many Sites With The WordPress REST API Connecting Content Silos: One CMS, Many Sites With The WordPress REST API
Connecting Content Silos: One CMS, Many Sites With The WordPress REST API
 
Actionview
ActionviewActionview
Actionview
 
Introduction to Html5
Introduction to Html5Introduction to Html5
Introduction to Html5
 
Single Page Web Apps As WordPress Admin Interfaces Using AngularJS & The Word...
Single Page Web Apps As WordPress Admin Interfaces Using AngularJS & The Word...Single Page Web Apps As WordPress Admin Interfaces Using AngularJS & The Word...
Single Page Web Apps As WordPress Admin Interfaces Using AngularJS & The Word...
 
Introduction to plugin development
Introduction to plugin developmentIntroduction to plugin development
Introduction to plugin development
 
Zend Framework 1.8 Features Webinar
Zend Framework 1.8 Features WebinarZend Framework 1.8 Features Webinar
Zend Framework 1.8 Features Webinar
 
Html 5 in a big nutshell
Html 5 in a big nutshellHtml 5 in a big nutshell
Html 5 in a big nutshell
 
Introduction to HTML
Introduction to HTMLIntroduction to HTML
Introduction to HTML
 
Class Intro / HTML Basics
Class Intro / HTML BasicsClass Intro / HTML Basics
Class Intro / HTML Basics
 
Let ColdFusion ORM do the work for you!
Let ColdFusion ORM do the work for you!Let ColdFusion ORM do the work for you!
Let ColdFusion ORM do the work for you!
 

Viewers also liked (6)

Supermondays twilio
Supermondays twilioSupermondays twilio
Supermondays twilio
 
Twilio OpenVBX and the 3rd Open Source Business Model
Twilio OpenVBX and the 3rd Open Source Business ModelTwilio OpenVBX and the 3rd Open Source Business Model
Twilio OpenVBX and the 3rd Open Source Business Model
 
Twilio - Monetizing SaaS - Jeff Lawson Cloudstock December 2010
Twilio - Monetizing SaaS - Jeff Lawson Cloudstock December 2010Twilio - Monetizing SaaS - Jeff Lawson Cloudstock December 2010
Twilio - Monetizing SaaS - Jeff Lawson Cloudstock December 2010
 
7 Principles of API Design - Waza
7 Principles of API Design - Waza7 Principles of API Design - Waza
7 Principles of API Design - Waza
 
From where OpenVBX came from to how we open sourced it
From where OpenVBX came from to how we open sourced itFrom where OpenVBX came from to how we open sourced it
From where OpenVBX came from to how we open sourced it
 
Developing Plugins on OpenVBX at Greater San Francisco Bay Area LAMP Group
Developing Plugins on OpenVBX at Greater San Francisco Bay Area LAMP GroupDeveloping Plugins on OpenVBX at Greater San Francisco Bay Area LAMP Group
Developing Plugins on OpenVBX at Greater San Francisco Bay Area LAMP Group
 

Similar to Abstracting functionality with centralised content

Gail villanueva add muscle to your wordpress site
Gail villanueva   add muscle to your wordpress siteGail villanueva   add muscle to your wordpress site
Gail villanueva add muscle to your wordpress site
references
 
Micro-ORM Introduction - Don't overcomplicate
Micro-ORM Introduction - Don't overcomplicateMicro-ORM Introduction - Don't overcomplicate
Micro-ORM Introduction - Don't overcomplicate
Kiev ALT.NET
 

Similar to Abstracting functionality with centralised content (20)

WordPress Structure and Best Practices
WordPress Structure and Best PracticesWordPress Structure and Best Practices
WordPress Structure and Best Practices
 
What's New in ZF 1.10
What's New in ZF 1.10What's New in ZF 1.10
What's New in ZF 1.10
 
Rapid Prototyping with PEAR
Rapid Prototyping with PEARRapid Prototyping with PEAR
Rapid Prototyping with PEAR
 
Childthemes ottawa-word camp-1919
Childthemes ottawa-word camp-1919Childthemes ottawa-word camp-1919
Childthemes ottawa-word camp-1919
 
Various Ways of Using WordPress
Various Ways of Using WordPressVarious Ways of Using WordPress
Various Ways of Using WordPress
 
WordPress as a Content Management System
WordPress as a Content Management SystemWordPress as a Content Management System
WordPress as a Content Management System
 
Modern Web Development with Perl
Modern Web Development with PerlModern Web Development with Perl
Modern Web Development with Perl
 
Sencha Touch Intro
Sencha Touch IntroSencha Touch Intro
Sencha Touch Intro
 
Custom Post Types and Meta Fields
Custom Post Types and Meta FieldsCustom Post Types and Meta Fields
Custom Post Types and Meta Fields
 
Wp meetup custom post types
Wp meetup custom post typesWp meetup custom post types
Wp meetup custom post types
 
PyCon APAC - Django Test Driven Development
PyCon APAC - Django Test Driven DevelopmentPyCon APAC - Django Test Driven Development
PyCon APAC - Django Test Driven Development
 
Magento Indexes
Magento IndexesMagento Indexes
Magento Indexes
 
The Way to Theme Enlightenment
The Way to Theme EnlightenmentThe Way to Theme Enlightenment
The Way to Theme Enlightenment
 
Windows Azure Storage & Sql Azure
Windows Azure Storage & Sql AzureWindows Azure Storage & Sql Azure
Windows Azure Storage & Sql Azure
 
Gail villanueva add muscle to your wordpress site
Gail villanueva   add muscle to your wordpress siteGail villanueva   add muscle to your wordpress site
Gail villanueva add muscle to your wordpress site
 
Introduction to Plugin Programming, WordCamp Miami 2011
Introduction to Plugin Programming, WordCamp Miami 2011Introduction to Plugin Programming, WordCamp Miami 2011
Introduction to Plugin Programming, WordCamp Miami 2011
 
Framework
FrameworkFramework
Framework
 
Micro-ORM Introduction - Don't overcomplicate
Micro-ORM Introduction - Don't overcomplicateMicro-ORM Introduction - Don't overcomplicate
Micro-ORM Introduction - Don't overcomplicate
 
HTML::FormHandler
HTML::FormHandlerHTML::FormHandler
HTML::FormHandler
 
WordPress as the Backbone(.js)
WordPress as the Backbone(.js)WordPress as the Backbone(.js)
WordPress as the Backbone(.js)
 

More from Michael Peacock

Refactoring to symfony components
Refactoring to symfony componentsRefactoring to symfony components
Refactoring to symfony components
Michael Peacock
 
Dance for the puppet master: G6 Tech Talk
Dance for the puppet master: G6 Tech TalkDance for the puppet master: G6 Tech Talk
Dance for the puppet master: G6 Tech Talk
Michael Peacock
 
Powerful and flexible templates with Twig
Powerful and flexible templates with Twig Powerful and flexible templates with Twig
Powerful and flexible templates with Twig
Michael Peacock
 
Introduction to OOP with PHP
Introduction to OOP with PHPIntroduction to OOP with PHP
Introduction to OOP with PHP
Michael Peacock
 
Phpne august-2012-symfony-components-friends
Phpne august-2012-symfony-components-friendsPhpne august-2012-symfony-components-friends
Phpne august-2012-symfony-components-friends
Michael Peacock
 
Evolution of a big data project
Evolution of a big data projectEvolution of a big data project
Evolution of a big data project
Michael Peacock
 
Real time voice call integration - Confoo 2012
Real time voice call integration - Confoo 2012Real time voice call integration - Confoo 2012
Real time voice call integration - Confoo 2012
Michael Peacock
 
Dealing with Continuous Data Processing, ConFoo 2012
Dealing with Continuous Data Processing, ConFoo 2012Dealing with Continuous Data Processing, ConFoo 2012
Dealing with Continuous Data Processing, ConFoo 2012
Michael Peacock
 
Data at Scale - Michael Peacock, Cloud Connect 2012
Data at Scale - Michael Peacock, Cloud Connect 2012Data at Scale - Michael Peacock, Cloud Connect 2012
Data at Scale - Michael Peacock, Cloud Connect 2012
Michael Peacock
 
PHP Continuous Data Processing
PHP Continuous Data ProcessingPHP Continuous Data Processing
PHP Continuous Data Processing
Michael Peacock
 
PHP North East Registry Pattern
PHP North East Registry PatternPHP North East Registry Pattern
PHP North East Registry Pattern
Michael Peacock
 

More from Michael Peacock (20)

Immutable Infrastructure with Packer Ansible and Terraform
Immutable Infrastructure with Packer Ansible and TerraformImmutable Infrastructure with Packer Ansible and Terraform
Immutable Infrastructure with Packer Ansible and Terraform
 
Test driven APIs with Laravel
Test driven APIs with LaravelTest driven APIs with Laravel
Test driven APIs with Laravel
 
Symfony Workflow Component - Introductory Lightning Talk
Symfony Workflow Component - Introductory Lightning TalkSymfony Workflow Component - Introductory Lightning Talk
Symfony Workflow Component - Introductory Lightning Talk
 
Alexa, lets make a skill
Alexa, lets make a skillAlexa, lets make a skill
Alexa, lets make a skill
 
API Development with Laravel
API Development with LaravelAPI Development with Laravel
API Development with Laravel
 
An introduction to Laravel Passport
An introduction to Laravel PassportAn introduction to Laravel Passport
An introduction to Laravel Passport
 
Phinx talk
Phinx talkPhinx talk
Phinx talk
 
Refactoring to symfony components
Refactoring to symfony componentsRefactoring to symfony components
Refactoring to symfony components
 
Dance for the puppet master: G6 Tech Talk
Dance for the puppet master: G6 Tech TalkDance for the puppet master: G6 Tech Talk
Dance for the puppet master: G6 Tech Talk
 
Powerful and flexible templates with Twig
Powerful and flexible templates with Twig Powerful and flexible templates with Twig
Powerful and flexible templates with Twig
 
Introduction to OOP with PHP
Introduction to OOP with PHPIntroduction to OOP with PHP
Introduction to OOP with PHP
 
Vagrant
VagrantVagrant
Vagrant
 
Phpne august-2012-symfony-components-friends
Phpne august-2012-symfony-components-friendsPhpne august-2012-symfony-components-friends
Phpne august-2012-symfony-components-friends
 
Evolution of a big data project
Evolution of a big data projectEvolution of a big data project
Evolution of a big data project
 
Real time voice call integration - Confoo 2012
Real time voice call integration - Confoo 2012Real time voice call integration - Confoo 2012
Real time voice call integration - Confoo 2012
 
Dealing with Continuous Data Processing, ConFoo 2012
Dealing with Continuous Data Processing, ConFoo 2012Dealing with Continuous Data Processing, ConFoo 2012
Dealing with Continuous Data Processing, ConFoo 2012
 
Data at Scale - Michael Peacock, Cloud Connect 2012
Data at Scale - Michael Peacock, Cloud Connect 2012Data at Scale - Michael Peacock, Cloud Connect 2012
Data at Scale - Michael Peacock, Cloud Connect 2012
 
PHP & Twilio
PHP & TwilioPHP & Twilio
PHP & Twilio
 
PHP Continuous Data Processing
PHP Continuous Data ProcessingPHP Continuous Data Processing
PHP Continuous Data Processing
 
PHP North East Registry Pattern
PHP North East Registry PatternPHP North East Registry Pattern
PHP North East Registry Pattern
 

Abstracting functionality with centralised content

  • 1. Abstracting functionality with Centralised Content Michael Peacock
  • 2. About me Senior Web Developer M.D. of design agency Peacock Carter Technical director for an online retailer Author www.michaelpeacock.co.uk me@michaelpeacock.co.uk @michaelpeacock
  • 3. What's in store? Setting the scene – a look at the problem centralised content solves Centralised content – what is it and how can it solve this problem Implementation – How we implemented centralised content with PHP and MySQL
  • 4. A sample bespoke CMS / basic e-commerce website Pages Blog entries Blog categories News articles Products Product Categories Events Users can buy products Users can rate products Users can comment on / review products Users can comment on blog entries
  • 5. Commenting Needed for both blog entries and products Create a simple library or function to create a comment for us Comments table in our database Table to link them to blog entries Table to link them to products
  • 7. What about searching Do we have search boxes for different aspects of the site? Do we use a complex searching system? Should we just let Google do it for us? “Can’t someone else do it?” Homer J Simpson
  • 8. What if in the future, once development is complete… Users need to be able to rate blog entries Users need to be able to purchase (book onto) events … and comment on them … … and rate them… Sounds like a pain!
  • 9. Let’s take a step back… And centralise our content!
  • 10. Centralised Content Useful architecture; especially for CMS projects MVC brings out the best in it Drupal “node” MVC would be nice, Drupal Used extensively in our own CMS and frameworks for the past year and a half;
  • 11. Content Typical content found on a CMS powered site: Pages Blog entries News articles Job vacancies Products Events Photographs / Image gallery
  • 12. Pages Name Heading Page content Meta data / title URL
  • 13. Blog Entries Name Heading Blog entry Meta data / title Leading image Leading paragraph Author URL
  • 14. News articles Name Heading News article Meta data / title Leading image Leading paragraph Author URL
  • 15. Job Vacancies Name Heading Job description Location Application deadline Salary Meta data / title Author URL
  • 16. Products Name Heading Product description Price Weight Image Meta data / title Author URL
  • 17. Events Name Heading Event description Location Date Start / End time Meta data / title Author URL
  • 18. Gallery Images Name Heading Image caption / description Camera data Location Image location Meta data / title Author URL
  • 19. Its all the same! (well, almost…) Name Heading Title URL / Path / Search Engine friendly name Primary content / description / details Meta data Creator / Author Active / Enabled Comments enabled / disabled
  • 20. …with extra bits depending on the type Products Price; Stock level; SKU; Weight Events Venue; Spaces; Price; Date; Start time; End time Gallery image Image file location; Camera details; Location Job vacancy Salary; Location; Start date; Type; Application date
  • 21. Content versions With content being centralised we can implement versioning more easily Record all versions of content Static content IDs which relate to the active version
  • 22. So; let’s centralise it! Core fields will make up our main table of content (content_versions) Content types will have their own table, containing type specific fields content_versions_* A content table (content) will store static data, such as author, creation date, ID, and reference the current active version certain toggle-able fields (active, commentable) should go here Regardless of the version in play, the content ID can be used to access the element, and won’t change.
  • 24. Within MVC Content model Models for each content type, extending the content model For administrative tasks (CMS) content controller to perform shared operations: toggle active, toggle comments, delete Content type controllers extend
  • 25. Content model Deals exclusively with content and content versions tables Getters and setters for core content fields Creating content: Create new content version Create new content record, pointing to the version Editing content Create new content version Log the old version ID in a versions log Update the content record to point to the new version
  • 26. Content: save public function save() { // are we creating a new content item? if( $this->id == 0 ) { /** create the content versions record */ $this->registry->getObject('db')->insertRecords( 'content_versions', $insert ); // record the ID $this->revisionID = $this->registry->getObject('db')->lastInsertID(); /** insert the content record */ $this->registry->getObject('db')->insertRecords( 'content', $insert ); // record the ID $this->id = $this->registry->getObject('db')->lastInsertID(); } else { // have we changed the revision, or just something from the content table? if( $this->revisionChanged == true ) { // make a note of the old revision ID for the history $this->oldRevisionID = $this->revisionID; /** insert the new content_versions record */ $this->registry->getObject('db')->insertRecords( 'content_versions', $insert ); // update the revisionID $this->revisionID = $this->registry->getObject('db')->lastInsertID(); /** record the history */ $this->registry->getObject('db')->insertRecords( 'content_versions_history', $insert); } /* update the content table */ $this->registry->getObject('db')->updateRecords('content', $update, 'ID=' . $this->id ); } }
  • 27. Product (i.e. a content type) model Extends content model Getters and setters for extended data for the content type Creating product
  • 28. Product: save public function save() { // Creating a new product if( $this->getID() == 0 ) { parent::setType( $this->typeID ); parent::save(); $this->saveProduct(); } else { // tells the parent, that the revision has changed, // i.e. that we didn't just toggle active / change something from the _content_table! $this->setRevisionChanged( true ); parent::save(); $this->saveProduct(); } }
  • 29. Product: Saving product data private function saveProduct() { /** insert product specific data */ // product version ID should be the same as the content version ID for easy maping $insert['version_id'] = $this->getRevisionID(); $this->registry->getObject('db')->insertRecords( 'content_versions_store_products', $insert ); // get the content ID $pid = $this->getID(); // categories // delete all associations with this product ID // insert new ones based off user input; $pid to reference product // shipping costs // delete all associations with this ID // insert new ones based off user input; $pid to reference product }
  • 30. A load of CRUD! Creating New features / content types we only need to code for the extended fields Reading Custom constructor in the child model Call setters for content type specific fields Call parent setters for core content Child method to iterate through / process fields to go to the template engine
  • 31. A load of CRUD! Updating Parent deals with all the core fields (no new work!) Insert (versions) extended fields Use the ID the parent gives to the content version, to make table mapping easier Deleting Assuming “deleting” just hides content from front and back-end Parent object updates content record to deleted – no extra work!
  • 32. Commenting Requires just one database table Can be used, without additional work, for all content types Each comment relates to a record in the content table
  • 33. ... commenting private function postComment( $id ) { require_once( FRAMEWORK_PATH . 'models/comment/comment.php'); $comment = new Commentmodel( $this->registry, 0 ); // tell the comment which content element it relates to $comment->setContent( $id ); $comment->setName( isset( $_POST['comment_name'] ) ? $_POST['comment_name']: '' ); $comment->setEmail( isset( $_POST['comment_email'] ) ? $_POST['comment_email']: '' ); $comment->setURL( isset( $_POST['comment_url'] ) ? $_POST['comment_url']: '' ); $comment->setComment( isset( $_POST['comment_comment'] ) ? $_POST['comment_comment']: '' ); $comment->setIPAddress( $_SERVER['REMOTE_ADDR'] ); $comment->setCommentsAutoPublished( $this->registry->getSetting('blog.comments_approved') ); $comment->setPageURL( $this->registry->getURLBits() ); if( $comment->checkForErrors() ) { /** error processing */ } else { // save the post $comment->save(); /** Redirect to the controller the user was on before, based on the URL */ } }
  • 34. Rating Again, just one table Directly relates to the appropriate content element Create it once; works for all content types – no future work for new content types
  • 35. Geo-tagging Either extend the content / versions tableOR Create a co-ordinates table and map it to the content table
  • 36. What else? Keyword tagging Keywords table Content keywords associations table Central functionality to add keywords and delete orphaned keywords Categories A content type (up for debate) – why? New table to map content to content Central functionality to manage associations
  • 37. Purchasing Provided content types have consistent cost / shipping fields, they can slot into the order pipeline Won’t work for all content types Makes conversion easy Make event purchasable? Make gallery image purchasable? Just add price fields, and indicate the content type can be purchased
  • 38. Purchasing By giving an image a price field, pre-existing e-commerce functionality can process it Piece of cake
  • 39. Hierarchies and ordering Ordering pages within the sites menu Moving pages within another page Ordering product categories Ordering blog entries Ordering news articles
  • 40. Searching Search the content & content versions table LEFT JOIN content_versions_* tables where appropriate Designate searchable fields Execute the query Enjoy integrated search results!
  • 41. Searching: Define our extended search fields private $extendedTablesAndFields = array( 'content_versions_news' => array( 'joinfield' => 'version_id', 'fields' => array( 'lead_paragraph' ) ) );
  • 42. Searching: Build our left joins $joins = ""; $selects = ""; $wheres = " "; $priority = 4; $orders = ""; foreach( $this->extendedTablesAndFields as $table => $data ) { $field = $data['joinfield']; $joins .= " LEFT JOIN {$table} ON content.current_revision={$table}.{$field} "; foreach( $data['fields'] as $field ) { $wheres .= " IF( {$table}.{$field} LIKE '%{$phrase}%', 0, 1 ) <> 1 OR "; $selects .= ", IF( {$table}.{$field} LIKE '%{$phrase}%', 0, 1 ) as priority{$priority} "; $orders .= " priority{$priority} ASC, "; $priority++; } }
  • 43. Searching: Query! $sql = "SELECT *, IF(content_types.reference='page',content.path,CONCAT(content_types.view_path,'/',content.path ) ) as access_path, REPLACE(substr(content_versions.content,1,100),'<p>','') as snippet, content_types.name as ct, IF( content_versions.name LIKE '%{$phrase}%', 0, 1 ) as priority0, IF( content_versions.title LIKE '%{$phrase}%', 0, 1 ) as priority1, IF( content_versions.heading LIKE '%{$phrase}%', 0, 1 ) as priority2, IF( content_versions.content LIKE '%{$phrase}%', 0, 1 ) as priority3 {$selects} FROM content LEFT JOIN content_types ON content.type=content_types.ID {$joins} LEFT JOIN content_versions ON content.current_revision=content_versions.ID WHERE content.active=1 AND content.deleted=0 AND ( IF( content_versions.name LIKE '%{$phrase}%', 0, 1 ) <> 1 OR IF( content_versions.title LIKE '%{$phrase}%', 0, 1 ) <> 1 OR IF( content_versions.heading LIKE '%{$phrase}%', 0, 1 ) <> 1 OR IF( content_versions.content LIKE '%{$phrase}%', 0, 1 ) <> 1 OR {$wheres} ) ORDER BY priority0 ASC, priority1 ASC, priority2 ASC, priority3 ASC, {$orders} 1";
  • 45. Simple access permissions public function isAuthorised( $user ) { if( $this->requireAuthorisation == false ) { return true; } elseif( $user->loggedIn == false ) { return false; } elseif( count( array_intersect( $this->authorisedGroups, $user->groups ) ) > 0 ) { return true; } else { return false; } }
  • 46. (Simple) Access permissions Single table mapping content to groups At content level, for content elements which require – cross reference the users groups with allowed groups
  • 47. Downloads / Files / Resources Create them as a content type Searchable based off name / description Store the file outside of the web root Make use of access permissions already implemented Make them purchasable
  • 48. Not just “content”! Other entities within an application which are similar Social Networks CRM’s
  • 49. Social Networks: Statuses Core table: status Creator Profile status posted on (use it for both statuses and “wall posts” The status Creation date
  • 50. Social Networks: Statuses Extended tables: Videos: YouTube Video URL Images: URL, Dimensions Links: URL, Title
  • 51. ... Build a stream Status streams Recent activity Viewing a profile’s “wall posts”
  • 52. Query the stream Part of a stream model SELECT t.type_reference, t.type_name, s.*, p.name as poster_name, r.name as profile_name FROM statuses s, status_types t, profile p, profile r WHERE t.ID=s.type AND p.user_id=s.poster AND r.user_id=s.profileAND ( p.user_id={$user} OR r.user_id={$user} OR ( p.user_id IN ({$network}) AND r.user_id IN ({$network}) ) ) ORDER BY s.ID DESC LIMIT {$offset}, 20
  • 53. Generate the stream For each stream record Include a template related to: the stream item type, e.g. video The context, e.g. Posting on your own profile Insert stream record details into that instance of the template bit
  • 54. foreach( $streamdata as $data ) { if( $userUpdatingOwnStatus ) { // updates to users "wall" by themselves $template->addBit( 'stream/types/' . $data['type_reference'] . '-me-2-me.tpl.php', $data ); } elseif( $statusesToMe ) { // updates to users "wall" by someone $template->addBit( 'stream/types/' . $data['type_reference'] . '-2me.tpl.php', $datatags ); } elseif( $statusesFromMe ) { // statuses by user on someone elses wall $template->addBit( 'stream/types/' . $data['type_reference'] . '-fromme.tpl.php', $datatags ); } else { // friends posting on another friends wall $template->addBit( 'stream/types/' . $data['type_reference'] . '-f2f.tpl.php', $datatags ); } }
  • 55. CRM’s People and organisations are very similar Centralise them! Entity Entity_Organisation Entity_Person Record Person <-> organisation relationships in a table mapping entity table onto itself
  • 56. Extending: Adding new content types Drop in a model Getters and setters for extended fields Save method to insert extended data Drop in an administrator controller Rely on parent for standard operations Pass CRUD requests / data to the model Drop in a front end controller Viewing: over-ride parent method & extend the select query Anything else: Add specific functionality here
  • 57. Why centralise your content / common entities? Eases content versioning Write functionality (commenting, rating, etc) once, and it works for all current and future content types – without the need for additional work Fixing bugs with abstracted features, fixes it for all aspects which use that feature Conversion is relatively easy – e.g. Turn a page into a blog entry. More so if they don’t extend the core table
  • 58. Some of the problems Can be a risk of content and content versions tables having too many fields Optimization bottle-neck – poorly performing and optimizing tables will cripple the entire site Versioning: Stores lots of data Sometimes you want functionality to differ across features (e.g. Product reviews <> comments) A bug in an abstract feature will be present in all aspects which use it
  • 59. phpMyAdmin is harder Managing content via phpMyAdmin is more difficult MySQL Views can help with listing and viewing content
  • 60. Thanks for listening www.michaelpeacock.co.uk me@michaelpeacock.co.uk @michaelpeacock  Please leave feedback!http://joind.in/2065