Full Article

How to Create a Custom Data Source in Symphony CMS 2

In Symphony a data source is the conduit between your content, which is stored in a database, and the web page you display to site visitors. It allows you to extract data from specified tables and to filter, sort and group that data before displaying it. Although Symphony offers considerable flexibility in defining data sources in the Symphony backend, you most likely will encounter situations where you would like to do a little more than Symphony offers.

A data source pulls from only one section. (A section defines a type of content, the fields that are used to collect the content and the data base tables where the content is stored. There is a one-to-one correspondence between fields and tables.) But suppose the content you want to display from one section depends on the content of another section, and suppose the dependency is more complex than a simple link, like linking comments to articles or articles to categories. A custom data source is one way to do this. You might be able to use two data sources to pull all the content from both sections and then do the merging with XSLT, but this could involve a lot of processing overhead, depending on the size of the content and the nature of the dependency between the sections.

I frequently encounter a Symphony beginner in the Forum who could use a custom data source but doesn't know how to build one, so I thought I would attempt to put together a tutorial. Obviously I can't cover every specific need but I will start with an overview and follow up with a few examples. (I should point out that a concept called data source chaining might provide what you need, so check that out in the documentation before launching into creating a custom data source. Also, special requirements can sometimes be met using XSLT; which way you choose to go might be determined by your relative levels of comfort with PHP and XSLT.)

The first thing you need to do before making any custom changes to a data source file is to prevent further editing in the backend. (All data source files are stored under workspace/data-sources.) If you don't do this and you later open and save the data source in the backend, all your customization will be lost. This is done by changing this function to return false instead of true:

              
public function allowEditorToParse(){
    return true;
}
            

(In Symphony 2 you can prevent editing by deleting this function because the default is false. But in Symphony 3 the default is true, so it's a good practice to include the function and return false in custom data sources.)

The grab function at the bottom of a backend generated data source file is where most of your customization will take place. Here is what it typically looks like if you create the data source using the backend editor:

              
public function grab(&$param_pool=NULL){
    $result = new XMLElement($this->dsParamROOTELEMENT);
    try{
        include(TOOLKIT . '/data-sources/datasource.section.php');
    }
    catch(FrontendPageNotFoundException $e){
        FrontendPageNotFoundExceptionHandler::render($e);
    }
    catch(Exception $e){
        $result->appendChild(new XMLElement('error', $e->getMessage()));
        return $result;
    }
    if($this->_force_empty_result) $result = $this->emptyXMLSet();
    return $result;
}
            

The function argument "param_pool" gives you access to the parameter pool so that you can add parameters for use later in displaying a web page. The first statement in the function sets the root element of the XML document. Immediately after this statement, and immediately before the return statement at the bottom, you can insert additional content using the methods (functions) of the XMLElement class. The try-catch-catch statements in between are where the content is added that you specified when you originally created the data source using the backend editor. That is, you can add custom content before and after the auto-generated content. You can also modify the auto-generated content or not use it at all. If you don't need auto-generated content you would simply delete all the statements except the first and last and replace them with your custom code. The result of all customization and the auto-generation is returned to the calling class for use by your XSLT template in displaying your page.

An example of a custom data source that simply adds parameters to the parameter pool is shown in a previous article. In this case there is no auto-generated content and no XML document is returned. If you need a data source similar to this you can copy the code, paste it into a file, place the file in workspace/data-sources, and then customize it to suit your needs. Other examples will follow.

Comments for this article

David Hund
21 Oct 10, 5:44am

Hi Carson, thanks a lot for this post. I am fairly new to Symphony but am loving it so far. I would love to understand better how to develop custom data-sources.

This post makes it a little bit clearer but, to be honest, I still don't quite get it. E.g. you speak of inserting custom 'additional content'. What exactly do you mean?

Could you post a follow-up with more (code) examples? That would be very helpful to me, and no doubt others too, who are starting out with Symphony.

Thanks, David

Carson 25 Oct 10, 6:29pm

Sorry about the delay David. I've been out of town for a few days. I will try to address your questions tomorrow.

Carson 28 Oct 10, 3:13pm

"...you speak of inserting custom 'additional content'. What exactly do you mean? "

I mean that you can add whatever you want to the XML to make it available to your XSL template. You can also add parameters to the parameter pool as the referenced example illustrates.

Typically, when adding data to the XML you will first query your database for the data that you want to add. This is done using the functions defined in class.mysql.php. You add the resultant data to the XML using the functions defined in class.xmlelement.php. When querying the database you are not limited to one section as you are in a backend generated data source.

Rather than me shooting in the dark, I suggest that you define a situation where a conventional data source doesn't suffice for you and then let me propose an approach using a custom data source.

VG
10 Nov 10, 2:03am

Carson: "I suggest that you define a situation where a conventional data source doesn't suffice for you"

Well, I have a situation: I have a section called "News" and a few entries in it. All I want to do is query the database to grab one random "News" entry and send it to the frontend.

Something like 1. SELECT * FROM News ORDER BY RAND() LIMIT 0,1; 2. Send to XML

Thank you in advance.

Carson 15 Nov 10, 2:47pm

@VG - The requirement that you describe can be met easily with a conventional data source. Just create a new DS using the back-end editor. Select your "News" section as the source. Sort on System ID in a random order. Show a maximum of 1 results per page and show page 1 of results. Select the included elements and you're done.

But I will take this case as an example and show you how to do it using a custom data source. Save the following code as a file named data.random_news.php in workspace/data-sources. Change the table and field names to the respective names in your "News" section. Add it to a page on your site and give it a try. Let me know if you encounter a problem.


<?php

Class datasourcerandom_news extends Datasource{
    
    public function about(){
        return array(
                 'name' => 'Random News',
                 'author' => array(
                        'name' => 'Carson Sasser',
                        'website' => 'http://tech.carsonsasser.com',
                        'email' => 'sassercw@cox.net'),
                 'version' => '1.0',
                 'release-date' => '2010-11-15T21:17:41+00:00');    
    }
    
    public function allowEditorToParse(){
        return false;
    }
    
    public function grab(&$param_pool=NULL){
        $result = new XMLElement('random-news');
        
        //get the title and entry_id of a random news article
        //sym_entries_data_23 is the title field table
        //sym_entries_data_30 is the publish field table
        $query = "SELECT title.entry_id, title.value 
                    FROM sym_entries_data_23 AS title, sym_entries_data_30 AS publish 
                    WHERE title.entry_id=publish.entry_id AND publish.value='yes' 
                    ORDER BY RAND() LIMIT 1";
        $data = mysql_query($query) or die('Error 1: ' . mysql_error());
        $row = mysql_fetch_assoc($data);
        $node = new XMLElement('title', $row['value']);
        $result->appendChild($node);
        $id = $row['entry_id'];

        //get the date of the randomly selected article
        //sym_entries_data_26 is the date field table
        $query = "SELECT value FROM sym_entries_data_26 WHERE entry_id={$id} LIMIT 1";
        $data = mysql_query($query) or die('Error 2: ' . mysql_error());
        $row = mysql_fetch_assoc($data);
        $node = new XMLElement('date', $row['value']);
        $result->appendChild($node);
        
        //get the author of the randomly selected article
        //sym_entries_data_27 is the author_id field table
        //sym_authors is the table of registered developers and authors
        $query = "SELECT author.first_name, author.last_name 
                    FROM sym_authors AS author, sym_entries_data_27 AS authid 
                    WHERE author.id=authid.author_id AND authid.entry_id={$id} 
                    LIMIT 1";
        $data = mysql_query($query) or die('Error 3: ' . mysql_error());
        $row = mysql_fetch_assoc($data);
        $node = new XMLElement('author');
        $node->setAttribute('first-name', $row['first_name']);
        $node->setAttribute('last-name', $row['last_name']);
        $result->appendChild($node);

        //get the body of the randomly selected article
        //sym_entries_data_25 is the body field table
        $query = "SELECT value_formatted FROM sym_entries_data_25 WHERE entry_id={$id} LIMIT 1";
        $data = mysql_query($query) or die('Error 4: ' . mysql_error());
        $row = mysql_fetch_assoc($data);
        $node = new XMLElement('body', $row['value_formatted']);
        $result->appendChild($node);

        return $result;
    }
}
Vlad Ghita
16 Nov 10, 2:20am

"Select your "News" section as the source. Sort on System ID in a random order. Show a maximum of 1 results per page and show page 1 of results."

Uhmm, I missed that.

Thank you very much for your help. I will test the customized version as soon as possible.

Make a comment:

*Required inputs. Your email address will not be published.