Various links will be placed here

The CMS singleton class

The CMS singleton class
----------------------------
January 27th, 2019


In the previous post I tried to present the reasons for writing a CMS framework from scratch and criteria to guide my development. Then I described the structure of the bitSimpleCMS, a small collections of functions written back in 2009 that provided very basic CMS functionality, which I copied in the repository of the new CMS to be restructured and expanded upon. There was no need for a more in depth explanation of the functions as they will change considerably in the future.

In this second post I'll start making changes to the code structure so it will reflect the way I do things nowadays. Of course it will be object based and the actual inclusion of the functions in a class structure will straigt forward and we'll end up with a smaller code because all global directives will disappear. But the main topic of this topic will be writing singleton classes in php.

Maybe it is not necessary to explain what objects and classes are but I'll throw a short explanation anyway. In a nutshell, an object is a code and data structure at the same time, allowing to associate attributes and behaviours under a single entity. In practice however, objects are usually seen as data structures that also have functions included. Using objects is not mandatory for writing good programs and actually there are good arguments against it. On the other hand, writing applications using objects can prove useful when thinking in terms of hierarchical entities, behavior derivation from general objects to more specific ones, type and variable referencing in some cases and so on. In other words, one should use the apropriate tool for a given job. Generally, this is not true.

PHP also makes the distinction between classes and objects. This becomes obvious when creating objects at run time. For instance, the following code

<?php
	$obj = new stdClass(); //  this creates and empty object, instantiated from stdClass
	$obj->hello = "Hello World\n"; //  this will create a new attribute in the object

	echo $obj->hello;
?>

should generate the following output when executed in the terminal:

[user@computer test]$ php test.php
Hello World
[user@computer test]$ 

We see here the the object we created and changed does not conform to any previously defined class template.

Finally we should discuss about singletons. This is a so named "pattern" that restricts the number of instances of a class to just one. No matter how many references we generate or use in our project there will only be one instance of the class used by all references. This is useful when we wish to coordinate actions across the entire application. This also introduces a global state in the application and some may argue against it, especially in the current trend for functional programming paradigm. In our case however, we want exactly this, a globally accessible instance of the CMS class.

Let's start with a simple class and a constructor in test.php:

<?php
	class TSingleton
	{
		function __construct()
		{
			//  write initialization code here
		}
	}

	$obj = new TSingleton();
	echo var_dump($obj);
?>

We can execute from the terminal and see the output that confirms that the object has been created:

[user@computer test]$ php test.php
/var/www/html/test.php:11:
class TSingleton#1 (0) {
}
[user@computer test]$ 

If we want to prevent the instatiation of the class when executing the new keyword we'll have to simply hide the constructor from being accessible from outside let's change it to a private method and re-execute the script:

<?php
	class TSingleton
	{
		private function __construct()
		{
			//  write initialization code here
		}
	}

	$obj = new TSingleton();
	echo var_dump($obj);
?>

[user@computer test]$ php test.php
PHP Fatal error:  Uncaught Error: Call to private TSingleton::__construct() from invalid context in /var/www/html/test.php:10
Stack trace:
#0 {main}
  thrown in /var/www/html/bitnova/test.php on line 10
[user@computer test]$ 

I am not so sure about the elegance of this solution, I would have preferred to be able to change the memory allocation mechanism and allow the instantiation through normal syntax like I do it in object pascal, but I don't know anything like this to be possible in php and even C++ or C# for that matter. What we'll do instead is to provide an alternate static method to be called for the instance:

<?php
	class TSingleton()
	{
		static private $Finstance = null;
		static function instance()
		{
			if (self::$Finstance != null) return self::$Finstance;		
			self::$Finstance = new TSingleton();
			return self::$Finstance;
		}

		private function __construct()
		{
			//  write initialization code here
		}
	}
	
	$obj = TSingleton::instance();
	echo var_dump($obj);
?>

[user@computer test]$ php test.php
/var/www/html/test.php:19:
class TSingleton#1 (0) {
}
[user@computer test]$

We again obtain the same result as before, so the object creation works and that's all there is. The static method checks the internal reference to be set to a class instance (if not it will create the object) and returns it. This changes the way we'll use the singleton class, because we don't necessarily need to save the reference in a variable, it suffices to call TSingleton::instance()->whatever_attribute_or_method in order to use it.

In the end, let's see how to CMS class would look like after including all the variables and functions we had in content_vars.php and content_funcs.php files, and how it is called in the index file:

<?php

    class TQuarkCMS //extends TRPCService
    {
        static private $Finstance = null;
        static function instance()
        {
            if (self::$Finstance != null) return self::$Finstance;
            
            self::$Finstance = new TQuarkCMS();
            return self::$Finstance;
        }
        
        private function __construct()
        {
            
        }
        
        //  Declare title, description, keywords and author tag vars
        var $site_title = "";
        var $site_description = "";
        var $site_keywords = "";
        var $site_author = "";
        
        //  Declare language references
        var $lang_idxs = array();
        var $lang_hrefs = array();
        
        //  Declare menu items
        var $menu_items = array();
        var $menu_hrefs = array();
        
        //  Current content page -- set default here
        var $idx_current_page = 0;
        
        //  Current language index -- set default here
        var $idx_current_lang = 0;
        
        //  Load content definitions from xml
        function loadContentDefs()
        {
            if (file_exists('content.xml'))
            {
                $xml = simplexml_load_file('content.xml');
                
                //  load site descriptor tags
                $this->site_title = $xml->title;
                $this->site_description = $xml->description;
                $this->site_keywords = $xml->keywords;
                $this->site_author = $xml->author;
                
                for ($i = 0; $i < sizeof($xml->lang); $i++)
                {
                    $this->lang_idxs[$i] = $xml->lang[$i]->idx;
                    $this->lang_hrefs[$i] = $xml->lang[$i]->href;
                }
                
                for ($i = 0; $i < sizeof($xml->item); $i++)
                {
                    for ($j = 0; $j < sizeof($xml->item[$i]->menu_item); $j++)
                    {
                        $this->menu_items[$j][$i] = $xml->item[$i]->menu_item[$j];
                    }
                    
                    $this->menu_hrefs[$i] = $xml->item[$i]->href;
                }
            }
            else die ("Cannot load content definitions.");
        }
        
        function GenerateLangIcons()
        {
            for ($i = 0; $i < count($this->lang_idxs); $i++)
            {
                echo '<a href="index.php?content_id='.$this->idx_current_page.'&lang_id='.$i.'"><img class="lang" src="'.$this->lang_idxs[$i].'"/></a>';
            }
        }
        
        function GenerateMenu()
        {
            for ($i = 0; $i < count($this->menu_items[$this->idx_current_lang]); $i++)
            {
                echo '<a class="menu_item" href="index.php?content_id='.$i.'&lang_id='.$this->idx_current_lang.'"><p>'.$this->menu_items[$this->idx_current_lang][$i].'</p></a>';
            }
        }
        
        function GenerateTitle()
        {
            echo '<p>'.$this->menu_items[$this->idx_current_lang][$this->idx_current_page].'</p>';
        }
        
        function GenerateContent()
        {
            include $this->lang_hrefs[$this->idx_current_lang].$this->menu_hrefs[$this->idx_current_page];
        }

        function setHeader()
        {
            echo '<title>'.$this->site_title.'</title>';
            echo '<meta name="description" content="'.$this->site_description.'"/>';
            echo '<meta name="keywords" content="'.$this->site_keywords.'"/>';
            echo '<meta name="author" content="'.$this->site_author.'"/>';           
        }
        
        function loadTemplate()
        {
            include 'template.html';
        }
        
        function run()
        {
            $content_id = -1;
            $lang_id = -1;
            
            $content_id = $_GET["content_id"];
            $lang_id = $_GET["lang_id"];
            
            if ($content_id > -1) $this->idx_current_page = $content_id;
            if ($lang_id > -1) $this->idx_current_lang = $lang_id;

            $this->loadContentDefs();
            $this->setHeader();
            $this->loadTemplate();
            
        }
        
    }
?>

The index file will only contain two lines of code for including the cms class and to call the run method:

<?php
    include 'quarkcms.php';
    
    TQuarkCMS::instance()->run();    
?>

and a very simple template file would something like this:

<link rel="stylesheet" href="css/style.css" type="text/css"/>

<div align="center">
    <div class="wrapper">
        <div class="page">
            <div class="menu"><?php TQuarkCMS::instance()->GenerateMenu(); ?></div>
            <div class="lang"><?php TQuarkCMS::instance()->GenerateLangIcons(); ?></div>
            <div class="title"><?php TQuarkCMS::instance()->GenerateTitle(); ?></div>
            <div class="content"><?php TQuarkCMS::instance()->GenerateContent(); ?></div>
        </div>
        
        <div class="poweredby"><a href="http://www.bitnova.ro" target="_blank"><img src="img/poweredby.png"/></a></div>           
    </div>
</div>

That is the entire cms at the moment. I'll be uploading the git repository of the cms on github in order for anyone to download and checkout any revision out of it.