40+ Useful Php tips for beginners – Part 1

By | March 29, 2012

In this series we are going to look into some useful tips and techniques that can be used to improve and optimise your php code. Note that these php tips are meant for beginners and not those who are already using mvc frameworks etc.

The Techniques

1. Do not use relative paths , instead define a ROOT path

Its quite common to see such lines :

require_once('../../lib/some_class.php');

This approach has many drawbacks :

It first searches for directories specified in the include paths of php , then looks from the current directory.
So many directories are checked.

When a script is included by another script in a different directory , its base directory changes to that of the including script.

Another issue , is that when a script is being run from cron , it may not have its parent directory as the working directory.

So its a good idea to have absolute paths :

define('ROOT' , '/var/www/project/');
require_once(ROOT . '../../lib/some_class.php');

//rest of the code

Now this is an absolute path and will always stay constant. But we can improve this further. The directory /var/www/project can change , so do we change it everytime ? No instead we make it portable using magic constants like __FILE__ . Take a closer look :

//suppose your script is /var/www/project/index.php
//Then __FILE__ will always have that full path.

define('ROOT' , pathinfo(__FILE__, PATHINFO_DIRNAME));
require_once(ROOT . '../../lib/some_class.php');

//rest of the code

So now even if you shift your project to a different directory , like moving it to an online server , the same code will run without any changes.

2. Dont use require , include , require_once or include_once

Your script could be including various files on top , like class libraries , files for utility and helper functions etc like this :

require_once('lib/Database.php');
require_once('lib/Mail.php');

require_once('helpers/utitlity_functions.php');

This is rather primitive. The code needs to be more flexible. Write up helper functions to include things more easily. Lets take an example :

function load_class($class_name)
{
    //path to the class file
    $path = ROOT . '/lib/' . $class_name . '.php');
    require_once( $path ); 
}

load_class('Database');
load_class('Mail');

See any difference ? You must. It does not need any more explanation.
You can improve this further if you wish to like this :

function load_class($class_name)
{
    //path to the class file
    $path = ROOT . '/lib/' . $class_name . '.php');
    
    if(file_exists($path))
    {
        require_once( $path ); 
    }
}

There are a lot of things that can be done with this :

Search multiple directories for the same class file.
Change the directory containing class files easily , without breaking the code anywhere.
Use similar functions for loading files that contain helper functions , html content etc.

3. Maintain debugging environment in your application

During development we echo database queries , dump variables which are creating problems , and then once the problem is solved , we comment them or erase them. But its a good idea to let everything stay and help in the long run

On your development machine you can do this :

define('ENVIRONMENT' , 'development');

if(! $db->query( $query )
{
    if(ENVIRONMENT == 'development')
    {
        echo "$query failed";
    }
    else
    {
        echo "Database error. Please contact administrator";
    }    
}

And on the server you can do this :

define('ENVIRONMENT' , 'production');

if(! $db->query( $query )
{
    if(ENVIRONMENT == 'development')
    {
        echo "$query failed";
    }
    else
    {
        echo "Database error. Please contact administrator";
    }    
}

4. Propagate status messages via session

Status messages are those messages that are generated after doing a task.

<?php
if($wrong_username || $wrong_password)
{
    $msg = 'Invalid username or password';
}
?>
<html>
<body>

<?php echo $msg; ?>

<form>
...
</form>
</body>
</html>

Code like that is common. Using variables to show status messages has limitations. They cannot be send via redirects (unless you propagate them as GET variables to the next script , which is very silly). In large scripts there might be multiple messages etc.

Best way is to use session to propagate them (even if on same page). For this there has to be a session_start on every page.

function set_flash($msg)
{
    $_SESSION['message'] = $msg;
}

function get_flash()
{
    $msg = $_SESSION['message'];
    unset($_SESSION['message']);
    return $msg;
}

and in your script :

<?php
if($wrong_username || $wrong_password)
{
    set_flash('Invalid username or password');
}
?>
<html>
<body>

Status is : <?php echo get_flash(); ?>
<form>
...
</form>
</body>
</html>

5. Make your functions flexible

function add_to_cart($item_id , $qty)
{
    $_SESSION['cart'][$item_id] = $qty;
}

add_to_cart( 'IPHONE3' , 2 );

When adding a single item you use the above function. When adding multiple items , will you create another function ? NO. Just make the function flexible enough to take different kinds of parameters. Have a closer look :

function add_to_cart($item_id , $qty)
{
    if(!is_array($item_id))
    {
        $_SESSION['cart'][$item_id] = $qty;
    }

    else
    {
        foreach($item_id as $i_id => $qty)
        {
            $_SESSION['cart'][$i_id] = $qty;
        }
    }
}

add_to_cart( 'IPHONE3' , 2 );
add_to_cart( array('IPHONE3' => 2 , 'IPAD' => 5) );

So now the same function can accept different kinds of output. The above can be applied in lots of places to make your code more agile.

6. Omit the closing php tag if it is the last thing in a script

I wonder why this tip is omitted from so many blog posts on php tips.

<?php

echo "Hello";

//Now dont close this tag

This will save you lots of problem. Lets take an example :

A class file super_class.php

<?php
class super_class
{
    function super_function()
    {
        //super code
    }
}
?>
//super extra character after the closing tag

Now index.php

require_once('super_class.php');

//echo an image or pdf , or set the cookies or session data

And you will get Headers already send error. Why ? because the "super extra character" has been echoed , and all headers went along with that. Now you start debugging. You may have to waste many hours to find the super extra space.

Hence make it a habit to omit the closing tag :

<?php
class super_class
{
    function super_function()
    {
        //super code
    }
}

//No closing tag

Thats better.

7. Collect all output at one place , and output at one shot to the browser

This is called output buffering. Lets say you have been echoing content from different functions like this :

function print_header()
{
    echo "<div id='header'>Site Log and Login links</div>";
}

function print_footer()
{
    echo "<div id='footer'>Site was made by me</div>";
}

print_header();
for($i = 0 ; $i < 100; $i++)
{
    echo "I is : $i <br />';
}
print_footer();

Instead of doing like that , first collect all output in one place. You can either store it inside variables in the functions or use ob_start and ob_end_clean. So now it should look like

function print_header()
{
    $o = "<div id='header'>Site Log and Login links</div>";
    return $o;
}

function print_footer()
{
    $o = "<div id='footer'>Site was made by me</div>";
    return $o;
}

echo print_header();
for($i = 0 ; $i < 100; $i++)
{
    echo "I is : $i <br />';
}
echo print_footer();

So why should you do output buffering :

  • You can change the output just before sending it to browser if you need to. Think about doing some str_replaces , or may be preg_replaces or may be adding some extra html at the end like profiler/debugger output
  • Its a bad idea to send output to browser and do php processing at the same time. Have you ever seen a website where there is a Fatal error in the sidebar or in a box in the middle of the screen. You know why that happens ? Because processing and output are being mixed.

8. Send correct mime types via header when outputting non-html content

Lets echo some xml.

$xml = '<?xml version="1.0" encoding="utf-8" standalone="yes"?>';
$xml = "<response>
  <code>0</code>
</response>";

//Send xml data
echo $xml;

Works fine. But it needs some improvement.

$xml = '<?xml version="1.0" encoding="utf-8" standalone="yes"?>';
$xml = "<response>
  <code>0</code>
</response>";

//Send xml data
header("content-type: text/xml");
echo $xml;

Note that header line. That line tells the browser that the content is xml content. So the browser can handle it correctly. Many javascript libraries also rely on header information.

Similarly for javascript , css , jpg image , png image :

Javascript

header("content-type: application/x-javascript");
echo "var a = 10";

CSS

header("content-type: text/css");
echo "#div id { background:#000; }";

9. Set the correct character encoding for a mysql connection

Ever faced a problem that unicode/utf-8 characters are stored in mysql table correctly , phpmyadmin also shows them correct , but when you fetch them and echo on your page they do not show up correctly. The secret is mysql connection collation.

$host = 'localhost';
$username = 'root';
$password = 'super_secret';

//Attempt to connect to database
$c = mysqli_connect($host , $username, $password);
		
//Check connection validity
if (!$c) 
{
	die ("Could not connect to the database host: <br />". mysqli_connect_error());
}
		
//Set the character set of the connection
if(!mysqli_set_charset ( $c , 'UTF8' ))
{
	die('mysqli_set_charset() failed');
}

Once you connect to the database , its a good idea to set the connections characterset. This is a must when you are working with multiple languages in your application.

Otherwise what will happen ? You will see lots of boxes and ???????? in non english text.

10. Use htmlentities with the correct characterset option

Prior to php 5.4 the default character encoding used is ISO-8859-1 which cannot display characters like À â etc.

$value = htmlentities($this->value , ENT_QUOTES , 'UTF-8');

Php 5.4 onwards the default encoding will be UTF-8 which will solve most problems , but still better be aware about it if your application is multilingual.

Part 2

Its here :: Part 2

About Silver Moon

A Tech Enthusiast, Blogger, Linux Fan and a Software Developer. Writes about Computer hardware, Linux and Open Source software and coding in Python, Php and Javascript. He can be reached at [email protected].

34 Comments

40+ Useful Php tips for beginners – Part 1
  1. Space-O Canada

    Thanks for your post, these tips are very helpful for novice PHP developers. Your points will help to update the dynamic website with just minor-changes. I’ll share this post in my circle.

  2. Rich Carthew

    Hey Silver Moon, some nice tips here, thanks for putting them up.

    RE: Mike on April 6. I think Mike should have substantiated his comments- it’s a good thing to help out the PHP community by suggesting other methods with perhaps some empirical evidence if he believes differently and has other knowledge, but to just leave a comment like that- I’m surprised he has the intelligence to work in development at all.

    Rich.

  3. mario

    A load_class() wrapper is not always suitable as replacement for direct include() statements. It’ll run scripts in a different local variable scope, so really just useful for central and unbound libraries.

    Excluding close tags is a good newcomer advise. It doesn’t solve the mentioned problem though, as trailing spaces are just one out of dozen error causes http://stackoverflow.com/questions/8028957/headers-already-sent-by-php . More professionally you should use the phptags tag tidier http://freecode.com/projects/phptags for that. (Leading and trailing whitespace, BOM, and short / long open tags covered). Or better yet get more well-versed with your IDE or use a real programmers editor.

  4. Richard

    I realy like your article and the way you explain the problem with your source code.

    But I found a small “bug” in your “add_to_cart” functions in paragraph 5. “Make your functions flexible”! You forgot to set the variables as index in the arrays so it is not very flexible, hmm? :-)

    Looking forward to read the other articles of the series!

  5. Maurits

    Thanks for this article! To continue Seppi’s work:

    For #8 Line two needs to be $xml .= “response (notice the .=)

    Also I dissagree with dropping brackets (#19), for example:

    if( $a ) $a_count++;
    if( $b ) $b_count++;
    if( $c ) $c_count++;
    if( $d ) $d_count++;

    In this case the code is very clear, as long as you comment it properly ofcourse.

    I missed the following tip:

    $count = count( $array );

    for( $i = 0; $i < $count; ++$i ) {

    }

    Where the count() call needs to be outside the for loop (which is 600% faster, disregarding the option to use foreach).

    Besides that I missed the recommendation of using the PDO class (http://php.net/PDO) and its prepared statements.

    Keep up the great work though! :)

  6. Jason

    Hey man, nice tips – a couple of neat tricks in here, a lot of common sense. I have some more insight for you from a programmer/sysadmin:

    When using sessions keep in mind the following:
    * They themselves kill performance, why? because when session_start() all other php scripts using session_start() requested by that client are blocked in turn until the others complete.
    This can cause all sorts of fun “blocking” issues with long running scripts and progress counter updates.

    * Pages that use sessions are by definition NOT cacheable. This means they have to be fetched *every time* from the web server. So be smart with what you make the client fetch.

    A good example is a shopping cart widget. You only need per user info for that widget, not the whole page – and even then, you only *need* cart info on the client side. consider using client side session storage where it makes sense (i.e. on the front end) so that your website will be highly cacheable.

    Once you have a highly cacheable site, then put varnish-cache in front of it and watch it fly! This will mitigate the need to cache database results/generated pages yourself. this is “Making the tools that should be doing the job, do their job!” or “This wheel is round, no need to reinvent it”.

    #28 * Sessions can be shared between multiple web servers via NFS quite nicely. This is a common technique used in multiple webserver clusters and keeps the database free of cruft and there is less logic to go wrong in your code :) In other words, make your sysadmin do his/her job properly!

  7. EllisGL

    I have a couple things:
    Single Quotes vs Double Quotes speed. I’ve seen bench marks saying one is faster than the other. I’ve run bench marks showing inconsistent numbers.

    On the the example with the increment ($i++), ++$i is faster by one op code IIRC – which is a micro optimization, but one that i’ve seen as reproducible.

    1. Binary Tides Post author

      Yes , thats why micro optimizations have not been talked about much in this article.
      In a language like php micro optimisations are the last ones to be considered if at all needed.

  8. mike

    This is by far the sloppiest and worst list of tips I have seen. Some are unjustified. Some just are plain dumb. Some are against the point of having those language constructs in place.

  9. Seppi

    This is an awesome article! I only have issues with a few points, but otherwise it’s really solid!

    For #7 I think you meant to make it `return “… ` as opposed to `$o = “…`

    For #18 you ought to consider removing the redundancies: `Utility::utility_a()` becomes `Utility::a()`

    For #20 your arguments are backwards. It’s callback and then array. (php 5.3.5). Also, since there isn’t really any performance difference(http://codepad.org/SxlCQXRF), one could argue that the foreach is clearer as it looks like a loop and easier to extend since one doesn’t have to create a new function just to change the callback.

    For #24 mention something about passing by reference: http://php.net/manual/en/language.references.pass.php

Leave a Reply

Your email address will not be published. Required fields are marked *