Composr Supplementary: A PHP/Composr coding primer

Written by Chris Warburton (ocProducts)
This tutorial will explain the fundamentals of PHP and Composr programming, with a focus around building mini-blocks in Composr.
It will work through various practical examples, building up key theoretical understanding in the process.


Creating mini-blocks from Third-Party Code

This section is a short example-driven tutorial on creating a new Composr block (actually a "mini-block") by using externally-provided code. This is a great way to get started with customising Composr to your needs, and providing some level of integration between your Composr installation and your favourite external websites.

Who Is This Aimed At?

  • Those with little to no programming experience, but with a willingness to learn
  • Composr webmasters who want a little more integration with their favourite sites and services
  • Programmers wanting to learn how to code for Composr

What You'll Achieve

  • Learn a little about the languages which drive Composr and the web in general:
    • PHP
    • JavaScript
    • CSS
    • HTML
  • Learn how to integrate existing code into Composr in a basic way
  • A new block which you can insert into any Comcode-enabled field on your Composr site
  • An addon to share with the Composr community

What You'll Need

  • A text editor, preferably one aimed at programmers. A word processor will not do, and neither will Windows Notepad (if you try to use Notepad it will cause you lots of headaches later!). Examples of good programming text editors are:
  • A working Composr installation (see the Composr documentation on how to get up and running). This doesn't necessarily have to be a live website, for example it could be installed on your home computer and only accessible to your home network.
  • Some HTML you would like to reference from inside Composr. Usually you can find lots of examples of this kind of thing under names like "embed code", "blog widget" and that kind of thing. It should be in the HTML format. For this example I'm using some code from the OpenStreetMap project, which I'll describe below.

Getting Started

In this example we're going to create a Composr "mini-block" out of the following code:

Code (HTML)

<iframe width="425" height="350" frameborder="0" scrolling="no" marginheight="0" marginwidth="0" src="http://www.openstreetmap.org/export/embed.html?bbox=-1.49107,53.37746,-1.48328,53.38272&amp;layer=mapnik" style="border: 1px solid black"></iframe><br /><small><a href="http://www.openstreetmap.org/?lat=53.38009&amp;lon=-1.487175&amp;zoom=16&amp;layers=M">View Larger Map</a></small>
 
This is taken from the OpenStreetMap site, and in this particular case shows a map of the main campus of the University of Sheffield. To get this code I clicked on the "Export" tab at the top of the map and chose "Embeddable HTML". The code appeared in the text box labelled "output". Lots of sites offer code like this, and the purpose of this tutorial is to make accessing such snippets of code really easy for users of our Composr sites.

What is a mini-block?

"Mini-blocks" are a simpler form of Composr's "blocks". On a Composr-powered site, the majority of the content (page content, forum posts, comments, etc.) is written as straightforward text. To give extra control over how this text will be displayed, the Comcode language can be used to specify things like "I want this text to be bold" or "I want this to be a link" or "I want this image here". To get even more power, a programmer can write some specific bits of functionality to display (usually) in a box, which Comcode writers can then embed in the content they write, in much the same way as an image can be embedded. These are called "blocks", and they have lots of useful features (like caching, to reduce the server load; parameters, to give them options,; etc.). A "mini-block" provides the same drop-in usage for Comcode users, but without some of the fancy extras that regular blocks have. This sacrifice makes them much easier to write.

Preparing To Make Our mini-block

A mini-block is written in the PHP language, a programming language which uses "plain" text files for storing its programs. We'll get to the contents of the file in a moment, but to tell Composr that a text file contains some PHP, we give the file a name that ends in ".php" (rather than, for example, ".txt" which is often used for "plain" text files).

For Composr to find the mini-block we need to put the file inside a folder called miniblocks inside the sources_custom folder at the location that you've installed Composr to. The final thing we need to know is the actual name of the file. This is up to you, but the convention is that blocks which are intended to be used in a large area, like the middle of a page, start with "main_", while those intended for use in side panels have file names starting with "side_". Whatever comes afterwards, up to the ".php", is up to you, but try to stick to English letters without accents, since other letters and characters may confuse some systems. For this example I will call our mini-block "main_openstreetmap.php", so we need to make a new, empty text file inside the programming editor and save it as "main_openstreetmap.php" inside the "mini-blocks" folder inside the "sources_custom" folder of the Composr installation.

If you don't have direct access to your Composr system, for example if you can only manage the files from a web interface, FTP or similar then you can simply save it to the drive on whichever computer you're using then upload it to your site when you're finished; though this can be a little tedious if you have to keep making small fixes and changes.

What to put in the mini-block?

With your empty PHP text file open in your programming editor, you can begin to write the mini-block. Due to the nature of PHP, as it was originally created to embed inside HTML files, the first thing we need to do is declare that we're going to start writing some PHP in our file. We do this by writing the following at the start of the file:

Code (PHP)

<?php
The next thing we should do is to write a short description of what we're about to do, so that we don't get confused by the code later. Such descriptions are called "comments", and we write a comment by telling PHP to ignore what we're about to say (otherwise it may get confused and try to run our sentences as if they were code). There are 2 ways to do this in PHP. The first is to write two slashes, followed by our comment, like this:

Code (PHP)

// This is my comment. PHP will ignore it.
PHP will ignore whatever is written after 2 slashes until the end of that line. If you want to write a comment which takes up several lines then you can either put 2 slashes at the start of each line, or you can put a slash followed by an asterisk to start the comment, and an asterisk followed by a slash to end it, like this

Code (PHP)

/* This is a comment. It carries on for as long as I want it to. Even this is still a comment. I can end it like this */
Using this notation we can put a description at the top of our mini-block, to make our mini-block look like this so far:

Code (PHP)

<?php
/* This is a mini-block to draw a map from OpenStreetMap.org.
It centres on the University of Sheffield by default. */

 
The next thing to do is to tell PHP what we want it to output. PHP is an elaborate text-rewriting system which, as it runs, gives out data. The data we want it to give out is text in the HTML format, so that visitors to our site will be able to display it in their web browsers. The exact text we want it to give out is the code we obtained from OpenStreetMap.org. Thus we first tell PHP that we want it to output something, which we do by using the "echo" command:

Code (PHP)

<?php
/* This is a mini-block to draw a map from OpenStreetMap.org.
It centres on the University of Sheffield by default. */

echo
Then, after a space, we say what we would like to have it output; the classic example being

Code (PHP)

echo "hello world"
Note that we can't just dump our HTML into the PHP, as PHP files allow HTML to be mixed in with the PHP, which will confuse the program. Instead we need to explicitly tell it that the HTML is a piece of text, and not something that it should try to run. We do this by quoting the text, which can be done with either single quotes 'like this' or double quotes "like this". Since our HTML contains some double quotes, and "quotes can't be "nested"" (unlike brackets (which can)) we should use single quotes. To end the echo statement we need to add a semicolon (;) outside of any quotes. Thus our mini-block looks like this:

Code (PHP)

<?php
/* This is a mini-block to draw a map from OpenStreetMap.org.
It centres on the University of Sheffield by default. */

echo '<iframe width="425" height="350" frameborder="0" scrolling="no" marginheight="0" marginwidth="0" src="http://www.openstreetmap.org/export/embed.html?bbox=-1.4911,53.37756,-1.48325,53.38262&amp;layer=mapnik" style="border: 1px solid black"></iframe><br /><small><a href="http://www.openstreetmap.org/?lat=53.38009&amp;lon=-1.487175&amp;zoom=16&amp;layers=M">View Larger Map</a></small>';
This suddenly looks a lot more complicated, but as far as PHP is concerned this is as simple as the 'echo "hello world";' example. It doesn't care whether the text is written in HTML or not, it's only the user's browser that cares. Also, keep in mind that all of the above HTML was copied straight from someone else's website/service, we didn't have to write it and we don't even need to care how it works (although some parts like "height", "lat", "lon" and "zoom" are pretty easy to guess).

Using the mini-block

That's all there is to writing a mini-block, although of course the PHP language is capable of far more than simply repeating something you tell it. Nevertheless, even this little mini-block should come in handy to those who would otherwise run screaming if they saw its contents. By saving it to our sources_custom/miniblocks directory it is already available to Composr users, who just need to write

Code

[block]main_openstreetmap[/block]
(or whatever else you called it, without the ".php" at the end).

I've attached a screenshot of this mini-block embedded on the homepage of a test installation of Composr:



Bundling as an addon

If you wish to turn your mini-block into a Composr addon that anyone can install then you can follow the instructions and diagrams in this section of the Composr programming framework tutorial. Composr, by default, gives administrators access to a "tree view" of all the addons which have been published on https://compo.sr. This means that, if you upload your addon to https://compo.sr, it will appear inside (almost) every Composr installation in the world, and can be downloaded and installed automatically with just a few clicks!

More Useful mini-blocks via functions

In the previous section, we saw how to write the PHP necessary for Composr to run our commands. In this section we will expand on this to create more interesting and useful mini-blocks, which do more than simply repeat what they're told.

Functions

One of the most widely used ideas in programming is that of the function. Like functions in Maths (such as sine and cosine), functions can be used in PHP to re-use some process which has been defined before. All that is required to use a function is knowing its name, knowing what it needs to be given to act upon (eg. sine and cosine need to be given an angle to work with) and having it available to your program (not every function is available all of the time, since it would take up far too much memory to have them all waiting around and not being used). What makes functions so powerful is that in order to use them, we don't need to care about how they do what they do (their "implementation"), all we have to know is how to make them run ("calling" them).

Using a function in PHP is the same as in Maths, where we just write the function's name, then put whatever we want it to act upon (its "arguments") inside some parentheses (brackets) next to the name. For instance "sine(2)". If we need to give a function multiple arguments then we put commas between them, for example if we wanted the maximum of a series of numbers we might say "max(6,3,98.5,12)". If a function doesn't require any arguments then we simply leave the brackets empty, for example if we want a random number we might write "random()". The difference between functions in Maths and functions in programming is that programming functions don't just turn numbers into other numbers, they can do anything that the language (in our case PHP) is capable of. Here are some examples of using PHP functions (with comments describing what they do):

Code (PHP)

// 2 whole number arguments. Gives us an image
// with a palette of 16,777,216 colours, a width of
// 800 pixels and a height of 600 pixels.
imagecreatetruecolor(800, 600);

// 1 text ("string") argument. Gives us the
// lowercase text 'hello world'.
strtolower('HELLO WORLD');

// No arguments. Gives us the "base URL"
// option of a Composr site
get_base_url();
 

Since a function can "give back" some value (we say it "returns" the value) we can pass this straight into another function as an argument by simply nesting the calls to the functions, like this:

Code (PHP)

// This will call the "get_base_url" function,
// which gives us a text string, and passes
// that string straight to the "strtoupper"
// function which makes it uppercase.
strtoupper(get_base_url());
 

We will use the variety of functions defined by PHP and Composr to spice up our map mini-block.

Using Functions In mini-blocks

Composr defines many functions, and a comprehensive description of them all can be found in the Application Programmers' Interface documentation. Here we will use a few simple functions to make the output of our map mini-block more useful. The functions we'll use are:
  • array, which takes any number of arguments of any type. It gives back an "array", or ordered list, containing whatever arguments you gave it. For example array('a','b','c','d',9,10,11,12) will return an array (list) of values ('a','b','c','d',9,10,11,12). Technically this isn't actually a function in PHP, but the distinction doesn't matter to a novice.
  • implode, which takes as arguments a string of text and an array of text strings. It returns a text string generated by sticking together each string in the array argument, with the string argument in between. For example implode('hello',array('a','b','cde','f')) will return a string of text 'ahellobhellocdehellof'.
  • strval, which takes an argument of any type and returns a text string representing it. For example strval(58.7) will return the text string '58.7' (which is not the same as the number 58.7, since that isn't a text string, it's a number).
  • get_option, which takes a string argument which should be the name of a Composr configuration option. It returns the value of that option. For example get_option('site_closed') returns (in my case, with an open site) 0.

The Minimodule Code

The following is the code of our improved minimodule. We put it in a file in the same way as in the first example. I've decided to call this file main_openstreetmap_options.php so that I can have both versions of the minimodule installed at once. The code still produces the HTML we took from openstreetmap.org like before, but rather than putting the whole thing in quotes and using it as-is, we split it up so that optional bits, like the values for width and height, can be swapped out for the values of Composr configuration options.
We then use the "echo" command to output each of these bits in turn, so that the browser sees them as one long piece of HTML. A few points to notice are that we keep the quote marks and commas of the HTML intact, which is important since the browser will be expecting them; we also put our calls to the get_option function inside a call to the strval function; this is because our options will give us numbers and we want to convert them into text string (which strval does for us); finally we add 0.01 to the longitude and latitude at one point, this is because the OpenStreetMap code uses a different value for the left, right, top and bottom of the map so we need to keep them slightly apart or else our map will have zero contents.

Code (PHP)

<?php
/* This is a minimodule to show a map from OpenStreetMap.org.
It uses site configuration options to determine what should be
displayed */

echo '<iframe width="';  // HTML
echo strval(get_option('map_width'));
echo '" height="';  // HTML
echo strval(get_option('map_height'));
echo '" frameborder="0" scrolling="no" marginheight="0" marginwidth="0" src="http://www.openstreetmap.org/export/embed.html?bbox=';  // HT$
echo strval(get_option('longitude'));
echo ',';  // HTML
echo strval(get_option('latitude'));
echo ',';  // HTML
echo strval(get_option('longitude') + 0.01);  // Give the map an area of 0.01x0.01
echo ',';  // HTML
echo strval(get_option('latitude') + 0.01);  // Give the map an area of 0.01x0.01
echo '&amp;layer=mapnik" style="border: 1px solid black"></iframe><br/><small><a href="http://www.openstreetmap.org/?lat=';  // HTML
echo strval(get_option('latitude'));
echo '&amp;lon=';  // HTML
echo strval(get_option('longitude'));
echo '&amp;zoom=';  // HTML
echo strval(get_option('map_zoom'));
echo '&amp;layers=M">View Larger Map</a></small>';  // HTML
 

This is starting to look complicated isn't it! We just need to keep in mind that everything inside /* and */ are just comments, they're not part of the code, as are the comments after the slashes (//). Everything that is just repeating the copy-pasted HTML from before has been given the comment "HTML". Disregarding those bits we see that the only new parts are the function calls and the "+0.01".

Notice that each line ends in a semicolon (;) like the "echo" line in our first example. It's very common to forget to put these, so it may help if you think of semicolons as the symbol for "and then". For example

Code (PHP)

1 + 2;
3 + 4;
 
Means "one plus two and then three plus four and then (end)". If you're having problems with your mini-block then I'd recommend you check each line for its semicolon, and check whether your opening and closing quotes match.

Required Composr Setup

This mini-block uses functions to get Composr options, however I've made up these options for the example. In order for this mini-block to work we need to add these options to the Composr site we're going to run it on (you probably won't want to do this on a live site if you're only experimenting. I do my experimenting on a local installation of Composr that I don't mind reinstalling whenever I mess it up ;) ). To do it we'll need to add a few files:

sources_custom/hooks/systems/config/map_width.php:

Code (PHP)

<?php

class Hook_config_map_width
{
    /**
     * Gets the details relating to the config option.
     *
     * @return ?array The details (null: disabled)
     */

    public function get_details()
    {
        return array(
            'human_name' => 'MAP_WIDTH',
            'type' => 'integer',
            'category' => 'BLOCKS',
            'group' => 'MAP_BLOCK',
            'explanation' => 'CONFIG_OPTION_map_width',
            'shared_hosting_restricted' => '0',
            'list_options' => '',
            'order_in_category_group' => 1,

            'addon' => 'openmap',
        );
    }

    /**
     * Gets the default value for the config option.
     *
     * @return ?string The default value (null: option is disabled)
     */

    public function get_default()
    {
        return '300';
    }
}
 

sources_custom/hooks/systems/config/map_height.php:

Code (PHP)

<?php

class Hook_config_map_height
{
    /**
     * Gets the details relating to the config option.
     *
     * @return ?array The details (null: disabled)
     */

    public function get_details()
    {
        return array(
            'human_name' => 'MAP_HEIGHT',
            'type' => 'integer',
            'category' => 'BLOCKS',
            'group' => 'MAP_BLOCK',
            'explanation' => 'CONFIG_OPTION_map_height',
            'shared_hosting_restricted' => '0',
            'list_options' => '',
            'order_in_category_group' => 1,

            'addon' => 'openmap',
        );
    }

    /**
     * Gets the default value for the config option.
     *
     * @return ?string The default value (null: option is disabled)
     */

    public function get_default()
    {
        return '300';
    }
}
 

sources_custom/hooks/systems/config/map_zoom.php:

Code (PHP)

<?php

class Hook_config_map_zoom
{
    /**
     * Gets the details relating to the config option.
     *
     * @return ?array The details (null: disabled)
     */

    public function get_details()
    {
        return array(
            'human_name' => 'MAP_ZOOM',
            'type' => 'integer',
            'category' => 'BLOCKS',
            'group' => 'MAP_BLOCK',
            'explanation' => 'CONFIG_OPTION_map_zoom',
            'shared_hosting_restricted' => '0',
            'list_options' => '',
            'order_in_category_group' => 1,

            'addon' => 'openmap',
        );
    }

    /**
     * Gets the default value for the config option.
     *
     * @return ?string The default value (null: option is disabled)
     */

    public function get_default()
    {
        return '16';
    }
}
 

sources_custom/hooks/systems/config/longitude.php:

Code (PHP)

<?php

class Hook_config_longitude
{
    /**
     * Gets the details relating to the config option.
     *
     * @return ?array The details (null: disabled)
     */

    public function get_details()
    {
        return array(
            'human_name' => 'LONGITUDE',
            'type' => 'float',
            'category' => 'BLOCKS',
            'group' => 'MAP_BLOCK',
            'explanation' => 'CONFIG_OPTION_longitude',
            'shared_hosting_restricted' => '0',
            'list_options' => '',
            'order_in_category_group' => 1,

            'addon' => 'openmap',
        );
    }

    /**
     * Gets the default value for the config option.
     *
     * @return ?string The default value (null: option is disabled)
     */

    public function get_default()
    {
        return '0.0';
    }
}
 

sources_custom/hooks/systems/config/latitude.php:

Code (PHP)

<?php

class Hook_config_latitude
{
    /**
     * Gets the details relating to the config option.
     *
     * @return ?array The details (null: disabled)
     */

    public function get_details()
    {
        return array(
            'human_name' => 'LATITUDE',
            'type' => 'float',
            'category' => 'BLOCKS',
            'group' => 'MAP_BLOCK',
            'explanation' => 'CONFIG_OPTION_latitude',
            'shared_hosting_restricted' => '0',
            'list_options' => '',
            'order_in_category_group' => 1,

            'addon' => 'openmap',
        );
    }

    /**
     * Gets the default value for the config option.
     *
     * @return ?string The default value (null: option is disabled)
     */

    public function get_default()
    {
        return '0.0';
    }
}
 

The next thing to do is to set up the translation strings needed by these options. In Composr pretty much everything is translatable, so we can't just give an English name and leave all non-English speakers unable to use the mini-block. Instead, what we gave to the human_name settings were code names which we can use to reference these options' names. Now we must define these names in at least one language.

The quickest way to do this is to put them in the "global" language file, which is available to all parts of the site. We're going to define the names for English, so make an  empty file "global.ini" in your Composr installation into the folder "lang_custom/EN". Put this into it:

Code (INI)

[strings]
MAP_BLOCK=Map block
MAP_WIDTH=Map width
MAP_HEIGHT=Map height
MAP_ZOOM=Zoom level
LONGITUDE=Longitude
LATITUDE=Latitude
CONFIG_GROUP_DESCRIP_MAP_BLOCK=Parameters to control the OpenStreetMap block.
 

(if the file already exists, add the above to the end of it, omitting the first line)

Save the file and go to Admin Zone > Setup > Configuration > Block options. There should be a section called "Map block" where you can enter the latitude, longitude, width, height and zoom level of the map.

(As an aside, we could have used block parameters instead of adding new configuration options, which would have actually been much simpler)

I hope you've not been too overwhelmed by this further exploration of Composr, PHP and programming in general :)

Controlling Data in mini-blocks Via variables

In this section we'll learn a little more about the PHP programming language and the various techniques it offers to us when we're writing code. In the last section, we learned how to use functions. These are a fundamental part of Composr programming, but there are a few more PHP features which we'll need to use as we dig further and further into Composr. To keep things as comfortable as possible we'll be learning them one at a time from the comfort of our familiar mini-blocks.

In the previous section we learned how to do interesting things by "calling" (running) functions. This time we'll take a look at variables and state.

Introduction

We saw that functions can give us values to work with, such as the value of a Composr configuration option. We also saw that we can nest function calls; that is, use the value given by a function call as an argument for another function call, such as:

Code (PHP)

strtoupper(get_base_url()); // Get the site's base URL and make it uppercase
 

We can nest as many such calls as we like, for example:

Code (PHP)

strlen(strval(strlen(get_base_url())))
 

This will give the number of digits in the length of a Composr site's "base URL" option. Remember that the arguments of a function are worked out first, then the function is called with those values. This means that get_base_url() will be worked out first, which gives a text string (from now on we'll refer to text as just "strings"), and this is given straight to the strlen function which gives us the number of letters and other characters in a string. This number is then sent to the strval function, which converts the number into a string of the number (ie. a number like 12 will become the string "12"). We then send this straight to the strlen function which, again, counts how many characters are in the string. Since the string is just that of a positive whole number, this will be the number of digits.

Variables

Using functions in the way shown above can get very confusing. Luckily there is a way to cut down on this confusion, once again borrowing an idea from Maths; the variable.

Rather than using variables to represent unknown values like in Maths (eg. "Solve for x") instead we can use variables to 'store' values temporarily, so that we can refer to them later. This is very useful if the result you want to work out depends on performing lots of steps, like the heavily nested function calls above. In PHP, variables are written as a name with a dollar sign, "$", at the beginning, for example $my_variable. Don't ask me why they force everyone to write dollar signs, PHP is full of silly rules.

We can 'store' a value in a variable by saying that the variable is equal to the value (we call this "assigning" the variable), like this:

Code (PHP)

$url = get_base_url();
 

Now we can use the short name $url instead of the longer function call get_base_url() (it's also more efficient, since the value only has to be worked out once). Notice the semicolon at the end, which means that this is an instruction to carry out (a "statement"). Assignment statements can do one of two things:
  1. Define a new variable (if the variable written on the left hasn't been used yet)
  2. Change the value stored in an existing variable (if the variable has been used before)

It is important to understand what PHP does when it sees an assignment statement, which is this:
  1. Work out the value of what is on the right hand side.
  2. Store this in the variable on the left hand side.

This means that the left hand side must be a variable name and nothing else and the right hand side must work out to the value we want and nothing else. This is different to the symmetric equalities found in Maths, where we can swap what's on each side around and it makes no difference, and also it means we can't get PHP to work out things like "$x + 20 = 25;", since "$x + 20" isn't a variable name (we can usually avoid such situations by using very simple algebra to rearrange the equation such that only the variable name is on the left).

Another very important difference between PHP and Maths is that in Maths we make statements which are 'timeless', ie. they are statements of truth which don't depend on how we say them. For example we could say "x = y" and "y = 5"; this, of course, means that "x = 5". In PHP however, everything is worked out in a sequence. If we try writing this example in PHP:

Code (PHP)

$x = $y;
$y = 5;
 
This will give an error, because when PHP reaches the first assignment instruction, it will complain that it doesn't know what $y is (since it's not reached the instruction which defines it yet). This leads to the idea of a program's state.

State

PHP will start at the top of whichever file it is given (eg. when a PHP file is accessed by a browser) and carry out each instruction it is given until it either reaches the end of the file or one of the instructions is to exit. On the way, it can go through other instructions if told to; for example a function call like get_base_url() is actually an instruction to go the definition of get_base_url (which lives deep within Composr's guts), run through those instructions, then come back when it's finished. The consequence is that PHP, unlike a person doing some Maths, is not smart enough to go looking through the code until it finds the definition of things; all it does is remember what it's been told previously.

The currently defined variables (and functions) along with their values makes up what is called the program's state (or 'memory'). As we saw, assignment can change the program's state by defining a new variable or changing the value stored in a variable. This second possibility is the one that causes the most confusion, since it seems to defy the laws of Maths. Consider the following, which all makes perfect sense if you understand the way PHP uses state:

Code (PHP)

$my_age = 22;
// Simple enough, $my_age now stores 22

$my_age_string = strval($my_age);
// $my_age_string now stores the string "22"

$my_age = $my_age + 1;
// The above line makes no sense if we think in terms of regular
// Maths, but it does make sense in PHP.
// First the right hand side will be worked out to give the value
// 23, then this will be assigned to $my_age, so that $my_age now
// stores the value 23. This line has changed the program's state

$my_new_age_string = strval($my_age);
// $my_new_age_string stores the string "23", but $my_age_string
// still stores "22" since it was defined before the state
// changed. Thus we've used the same code, "strval($my_age)",
// and received different results, because it depends on the
// state of the program; specifically, the state of $my_age.
 

Since we can change the value of our variables at any time, we have to be careful about any assumptions we make about these values. For example, just because you see $my_variable = 12; in some part of the code, this doesn't mean that you can write some new code somewhere else that uses $my_variable and assume it is 12, since the state may have changed in-between, or your code might run before that definition is made (in fact PHP has a feature called scope which will stop you making such assumptions, which will be explained in a later section).

Don't worry if you find the idea of state difficult to grasp at first; although it is very powerful, stateful programming can be fraught with bugs and is very hard to model and describe Mathematically (ie. it's very hard to prove whether state is being used correctly).

Personally, I favour defining new variables for things as I go (a loose form of the 'single assignment' programming style), rather than using a few variables and constantly changing the values they store. Although re-using variables makes more efficient use of memory (we overwrite any old values we don't need any more, rather than remembering them) it can cause easily avoidable headaches. For example, consider this piece of code:

Code (PHP)

// Get the site's base URL
$address = get_base_url();

// Send the user an HTML "anchor" which links to the site's base URL
echo '<a href="';
echo htmlentities($address);  // htmlentities turns strings into a form that won't interfere with HTML
echo '">Homepage</a>';

// Take some parameters from a submitted form
$name = post_param_string('name');
$age = post_param_integer('age');
$address = post_param_string('address');

// Send a message to the user
echo '<p>Hello ';
echo $name;
echo ' of ';
echo $address;
echo ', I see that you are ';
echo strval($age);
echo ' years old.';
echo '</p>';

// Give the user another homepage link
echo '<a href="';
echo htmlentities($address);
echo '">Go back to the homepage</a>';
 

Here we've used the variable $address for storing both the website's address and the user's home address. This has caused a bug since the "Go back to the homepage" link won't work, as it's trying to link to the user's home address! This bug isn't completely obvious, and PHP certainly won't spot it. We can fix it by either repeating the line $address = get_base_url(); between showing the message and showing the second link; or we can use a different name, like $user_address, for the user's home address. I'd prefer to do the latter, since then we don't have to worry about what's happened to $address; we can reuse $address anywhere we like without having to worry about what's stored inside it, since we never change it.

A mini-block example

Now that we've learned about variables, let's see how we can use them to make a seemingly complicated mini-block much simpler. Here we find out how many site points the current user has spent and has available to spend ("points" are a Composr feature where users get rewarded for contributing to the site), we store these in variables then we use echo to send the user an HTML image. The image uses Google Chart API to automatically generate a pie chart showing these values. Here's the code:

Code (PHP)

<?php
/* This will use Google Chart API to draw a graph of the current
   user's points availability. */


// First we get the current user ID
$userid = get_member();

// Now we get information about their points usage

// Load sources/points.php where the functions are defined
require_code('points');

// Call the functions and store their values in some variables
$available = available_points($userid);
$used = points_used($userid);

// Now we create a pie chart of these results. The way we do this
// is to send the user an HTML image with the URL containing the
// data, such that Google can turn it into a pie chart.
// The details of how these URLs work can be found at
// http://code.google.com/apis/chart
// An HTML image is written as <img src="url" alt="text" /> where
// url is the image's URL and text is a text alternative for
// those who browse in a text-only way.

// We start the image declaration
echo '<img src="';

// Then we set the start of the URL to Google's chart API
echo 'https://chart.googleapis.com/chart?';

// Now we put all of our options at the end of the URL
echo 'cht=p3';  // We want a 3D pie chart
echo '&chtt=Your Points';  // The chart title
echo '&chco=0000FF';  // Make the chart blue
echo '&chs=300x150';  // Make it 300 pixels wide and 150 pixels tall
echo '&chd=t:';  // Start declaring the data
echo strval($used);  // The amount of points used
echo ',';  // Separator
echo strval($available);  // The amount of unused points
echo '&chds=0,';  // The smallest data value
echo strval($available);  // The largest data value
echo '&chl=Used|Available';  // Give labels to the data

// Now we give a text alternative and finish the declaration
echo '" alt="Your Points: ';
echo strval($used);
echo ' used, ';
echo strval($available);
echo ' available." />';
 

The parameters we add to the end of the image URL may seem incomprehensible, but they are described with plenty of examples on Google's site. I hope you have fun playing with variables, state and Google's chart generator. I've saved this to my Composr test installation as sources_custom/miniblocks/main_userpoints.php and here is a screenshot of the mini-block used in a news article, via the Comcode [block]main_userpoints[/block] (after I'd gambled away some points in the points store ;) ). Every user who views the article will see a chart of their own points.



Understanding PHP and its Types

In this section we will learn about PHP's type system and the meaning of our programs. We won't directly build a Composr component here, but we will learn how to correctly handle different types of data which will be important as we progress.

Semantics

A programming system can usually be considered as 2 different parts; syntax and semantics. Syntax is the particular way that things need to be written in order for them to be "well formed" or "syntactically correct"; in other words, syntax separates valid programs from invalid programs based on how they're written (for example my$ =; is clearly invalid PHP). Semantics on the other hand, separates valid and invalid programs based on their behaviour or meaning. This is much trickier, since misbehaving programs are much harder to spot than incorrectly written ones, and in order to spot "bad" behaviour we need to know what "good" behaviour is, and even what we mean by "behaviour".

In PHP, as we learned in the last tutorial, the "meaning" of the code we write is that the computer will start at the first line of the first file it is given and treat each statement it finds as an instruction to carry out (joined together by semicolons which mean "and then"), until it either runs out of instructions or is told to stop. We also saw that these instructions can modify the state of the computer. Computer Scientists would call this a form of "operational semantics"; the meaning of the code depends on some physical operation being performed, in this case the meaning of the code is the changes it makes to the state of the computer (its memory).

This simple definition allows us to spot that code like

Code (PHP)

$x = 0;
$x = $x;
 
is 'wrong' because we know that the second line will never change the state, and thus is 'meaningless' from the point of view of PHP. In this case the mistake is harmless, since by definition it doesn't change the behaviour. There are much harder problems to deal with though, where the incorrect instruction depends on the state of the program when it is run, and we can use much more sophisticated techniques to find them.

Types

So far in the course of our PHP programming we've been using lots of values for things, but not paid much attention to what they mean to PHP. For example, we've learned that in PHP, text is referred to as a string and that there's a difference between a number like 42 and a string like "42". Understanding more about the semantics of PHP values, and in particular its type system allows us to spot and prevent many mistakes in the behaviour of our programs.

The type of some value, in a very non-rigourous sense, depends on both the way we represent that value and what we can do with it. Computers are built out of circuits which carry electricity, but the way these circuits are made causes the electric signals to behave like numbers. Thus the most basic kind of thing, or type of value, that a computer can handle is (whole) numbers, since they're built into the hardware of the machine.

In a similar way to using circuits that behave like numbers, we can make numbers behave like other types of things, if handled correctly. For example we can represent fractions with whole numbers by using scientific notation, such as '15×10-4' to mean '0.0015'. We can represent letters and other characters as numbers if we follow a scheme (an encoding) like '01 is A, 02 is B, 03 is C…', and if we have text then we can represent more complex types of thing, like PHP code and HTML pages, by inventing syntax to write them in.

Because all of these values are, ultimately, numbers (and then electric signals in the computer), the computer itself doesn't know if a value is meant to represent something of another type or not. Thus it is important to know what type of values we are dealing with in our programs if we're to handle them correctly. For example, we may try to write out the string "CAR" by saying:

Code (PHP)

echo "C" + "A" + "R";
 
However, if PHP is using the text representation described above (it doesn't, but this won't affect the examples) then this will actually be an instruction to do:

Code (PHP)

echo 03 + 01 + 18;
 
This is obviously not what we were expecting, since it's the number 22. There's been a mistake in our use of types here. However, it gets worse. We now have the number 22, but we were expecting a string so we're going to make another type error as we treat 22 as a string, which will end up being a "V". As far as the computer is concerned, "V" is the result we wanted, since that's what we asked for, but from our point of view this data has become corrupted. This is why semantics is difficult to get right.

Luckily, PHP doesn't blindly assume that we're treating types correctly. Instead, in PHP each value contains information about what type it is, and if we try to do things which don't make sense for that type of value (such as summing strings like above) then it will give us an error message or do an automatic conversion. Note that it is dangerous to rely on automatic conversion compared to specifying exactly what you mean.

If we want to fix the above example, and attach one string to the end of another (as opposed to summing them), then we can use the concatenation operator, a full stop ".", rather than the addition operator, the plus "+". This means we can write our example correctly as:

Code (PHP)

echo "C" . "A" . "R";
 
This won't give a type error, and it won't corrupt our values, and it won't be forced to do an automatic conversion. The downside, of course, is that we can't freely mix strings and numbers, since they're different types. However, it's quite easy to convert numbers into strings (just write them out in decimal), and the strval function will do this for us. For example, if we want to write out the number of cars, which is stored in a variable called $car_num, then we shouldn't say:

Code (PHP)

echo "There are " . $car_num . " cars";
 
Since this is a type error (we're trying to attach a number to some strings). Actually, it will work, because PHP will do automatic conversions. We should write it correctly as:

Code (PHP)

echo "There are " . strval($car_num) . " cars";
 

Conclusion

Although types are very useful for finding programming mistakes, like most of PHP its type system is limited to just the handful that have been hard-coded into the implementation by its developers. For example, we're not allowed to define our own types (eg. angles) or operators (eg. vector product), there are no higher-level types (types of types, eg. coordinates) and we can't define our own encodings (eg. an HTML type). We can overcome some of these difficulties by using PHP's primitive "object" system.

See also


Feedback

Please rate this tutorial:

Have a suggestion? Report an issue on the tracker.