Abstracting functionality with centralised content

60
Abstracting functionality with Centralised Content Michael Peacock

description

 

Transcript of Abstracting functionality with centralised content

Page 1: Abstracting functionality with centralised content

Abstracting functionality with Centralised Content

Michael Peacock

Page 2: Abstracting functionality with centralised content

About me

• Senior Web Developer• M.D. of design agency Peacock Carter• Technical director for an online retailer• Author

• www.michaelpeacock.co.uk• [email protected]• @michaelpeacock

Page 3: Abstracting functionality with centralised content

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

Page 4: Abstracting functionality with centralised content

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

Page 5: Abstracting functionality with centralised content

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

Page 6: Abstracting functionality with centralised content

Database

Page 7: Abstracting functionality with centralised content

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

Page 8: Abstracting functionality with centralised content

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!

Page 9: Abstracting functionality with centralised content

Let’s take a step back…

• And centralise our content!

Page 10: Abstracting functionality with centralised 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;

Page 11: Abstracting functionality with centralised content

Content

Typical content found on a CMS powered site:• Pages• Blog entries• News articles• Job vacancies• Products• Events• Photographs / Image gallery

Page 12: Abstracting functionality with centralised content

Pages

• Name• Heading• Page content• Meta data / title• URL

Page 13: Abstracting functionality with centralised content

Blog Entries

• Name• Heading• Blog entry• Meta data / title• Leading image• Leading paragraph• Author• URL

Page 14: Abstracting functionality with centralised content

News articles

• Name• Heading• News article• Meta data / title• Leading image• Leading paragraph• Author• URL

Page 15: Abstracting functionality with centralised content

Job Vacancies

• Name• Heading• Job description• Location• Application deadline• Salary• Meta data / title• Author• URL

Page 16: Abstracting functionality with centralised content

Products

• Name• Heading• Product description• Price• Weight• Image• Meta data / title• Author• URL

Page 17: Abstracting functionality with centralised content

Events

• Name• Heading• Event description• Location• Date• Start / End time• Meta data / title• Author• URL

Page 18: Abstracting functionality with centralised content

Gallery Images

• Name• Heading• Image caption / description• Camera data• Location• Image location• Meta data / title• Author• URL

Page 19: Abstracting functionality with centralised content

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

Page 20: Abstracting functionality with centralised content

…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

Page 21: Abstracting functionality with centralised content

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

Page 22: Abstracting functionality with centralised content

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.

Page 23: Abstracting functionality with centralised content

Core database

Page 24: Abstracting functionality with centralised content

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

Page 25: Abstracting functionality with centralised content

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

Page 26: Abstracting functionality with centralised content

Content: savepublic 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 ); }}

Page 27: Abstracting functionality with centralised content

Product (i.e. a content type) model

• Extends content model• Getters and setters for extended data for the

content type• Creating product

Page 28: Abstracting functionality with centralised content

Product: savepublic 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(); }}

Page 29: Abstracting functionality with centralised content

Product: Saving product dataprivate 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 }

Page 30: Abstracting functionality with centralised content

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

Page 31: Abstracting functionality with centralised content

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!

Page 32: Abstracting functionality with centralised content

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

Page 33: Abstracting functionality with centralised content

... commentingprivate 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 */ } }

Page 34: Abstracting functionality with centralised content

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

Page 35: Abstracting functionality with centralised content

Geo-tagging

• Either extend the content / versions table

OR

• Create a co-ordinates table and map it to the content table

Page 36: Abstracting functionality with centralised content

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

Page 37: Abstracting functionality with centralised content

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

Page 38: Abstracting functionality with centralised content

Purchasing

• By giving an image a price field, pre-existing e-commerce functionality can process it

• Piece of cake

Page 39: Abstracting functionality with centralised content

Hierarchies and ordering

• Ordering pages within the sites menu• Moving pages within another page• Ordering product categories• Ordering blog entries• Ordering news articles

Page 40: Abstracting functionality with centralised content

Searching

• Search the content & content versions table• LEFT JOIN content_versions_* tables where

appropriate– Designate searchable fields

• Execute the query• Enjoy integrated search results!

Page 41: Abstracting functionality with centralised content

Searching: Define our extended search fields

private $extendedTablesAndFields = array(

'content_versions_news' => array( 'joinfield' => 'version_id', 'fields' => array( 'lead_paragraph' ) )

);

Page 42: Abstracting functionality with centralised content

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++; }}

Page 43: Abstracting functionality with centralised content

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";

Page 44: Abstracting functionality with centralised content

Searching: Results

Page 45: Abstracting functionality with centralised content

Simple access permissionspublic 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;}

}

Page 46: Abstracting functionality with centralised content

(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

Page 47: Abstracting functionality with centralised content

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

Page 48: Abstracting functionality with centralised content

Not just “content”!

• Other entities within an application which are similar

– Social Networks

– CRM’s

Page 49: Abstracting functionality with centralised content

Social Networks: Statuses

Core table: status

– Creator– Profile status posted on (use it for both statuses

and “wall posts”– The status– Creation date

Page 50: Abstracting functionality with centralised content

Social Networks: Statuses

Extended tables:

– Videos: YouTube Video URL– Images: URL, Dimensions– Links: URL, Title

Page 51: Abstracting functionality with centralised content

... Build a stream

• Status streams

• Recent activity

• Viewing a profile’s “wall posts”

Page 52: Abstracting functionality with centralised content

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.profile AND ( 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

Page 53: Abstracting functionality with centralised content

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

Page 54: Abstracting functionality with centralised content

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 );}

}

Page 55: Abstracting functionality with centralised content

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

Page 56: Abstracting functionality with centralised content

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

Page 57: Abstracting functionality with centralised content

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

Page 58: Abstracting functionality with centralised content

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

Page 59: Abstracting functionality with centralised content

phpMyAdmin is harder

• Managing content via phpMyAdmin is more difficult

• MySQL Views can help with listing and viewing content

Page 60: Abstracting functionality with centralised content

Thanks for listening

• www.michaelpeacock.co.uk• [email protected]• @michaelpeacock

• Please leave feedback!http://joind.in/2065