Showing posts with label best practice. Show all posts
Showing posts with label best practice. Show all posts

Thursday, May 27, 2010

Readable PHP code #2 Make your API handle more!

Context

APIs are often designed to operate with scalars. As described in the following example, the function addContact() operates on a single element:

<?php
class User {
    protected 
$contacts = array();

    function 
addContact($contact) {
        
$this->contacts[] = $contact;
    }
}
?>

In a context where many contacts have to be added using the above API, code usually looks like:

<?php
// Some contacts
$contacts = array("Paul""John""Maria");

$user = new User();
// Looping over contacts to add them
foreach ($contacts as $contact) {
    
$user->addContact($contact);
}
?>

Inner looping

A way to avoid repeating this looping everywhere would be to design the API to work with array of contacts:

<?php
class User {
    protected 
$contacts = array();

    
function addContacts($contacts) {
        
foreach ($contacts as $contact) {
            
$this->contacts[] = $contact;
        }
    }

}
?>

This might make the code, where loops are in use, somewhat clearer:

<?php
// Some contacts
$contacts = array("Paul""John""Maria");

$user = new User();
$user->addContacts($contacts);
?>

With the benefit of speeding the processing a little bit as only one function call is issued!

The side effect is that adding only one contact is not as elegant:

<?php
$contact "Julia";

$user = new User();
$user->addContacts(array($contact));
?>

A nice PHP trick

The good news is that PHP provides a nice array cast operator: (array) which will transform a scalar value into an array. As described on the manual page about arrays:

"For any of the types: integer, float, string, boolean and resource, converting a value to an array results in an array with a single element with index zero and the value of the scalar which was converted. In other words, (array)$scalarValue is exactly the same as array($scalarValue)."

Previous example can be transformed to play nicely with both scalars and arrays:

<?php
class User {
    protected 
$contacts = array();

    function 
addContacts($contacts) {
        foreach (
(array) $contacts as $contact) {
            
$this->contacts[] = $contact;
        }
    }
}
?>

Function addContacts() can now be used the following way with scalars:

<?php
$contact 
"Julia";

$user = new User();
$user->addContacts($contact);
?>

OOPs!

This is a nice trick to define APIs to be used with both scalars and arrays, however this will not work when using objects! PHP is able to cast an objects as an array, but this will give you an access to its properties which is not the intended purpose.

If your contacts are objects you will have to modify the addContacts() function to something like:

<?php
class User {
    protected 
$contacts = array();

    function 
addContacts($contacts) {
        
if (is_array($contacts)) {
            foreach (
$contacts as $contact) {
                
$this->contacts[] = $contact;
            }
        } else {
            
$this->contacts[] = $contacts;
        }
    
}
}
?>

This may not be as elegant as the array casting method. However it will enable your API to work seemlessly with both scalars and arrays when objects are used.

The benefit of working with an array capable API is that you might sometimes optimize the operations. For example, in the case of retrieving or deleting elements from a database, you might want to use the "WHERE id IN (...)" syntax to match multiple elements rather than one by one. In the X examples that has been used in this article, an interesting optimization is to use the native array_merge() function which avoids reinventing the wheel by looping over elements using a foreach construct and adding elements one by one:

<?php
class User {
    protected 
$contacts = array();

    function 
addContacts($contacts) {
        if (
is_array($contacts)) {
            
$this->contacts array_merge($this->contacts$contacts);
        
} else {
            
$this->contacts[] = $contacts;
        }
    }
}
?>

Conclusion

In this article we have seen the advantages of creating an API which can handle multiple elements at once. If there is a benefit in terms of speed (which heavely depends on your business logic), don't forget that code readability is of higher importance too! Hopefully, this tip should improve both

For those who mind about performance, doing:

<?php
// Adding 5.000.000 contacts
$user->addContacts(range(15e6));
?>

is about 40% faster than:

<?php
foreach (range(15e6) as $contact) {
    
$user->addContact($contact);
}
?>

Thanks to Paul Dragoonis, Paul Borgermans and Jérôme Renard for reviewing this article

Monday, October 6, 2008

Readable PHP code #1 Return ASAP

Introduction


This is the first article of a series I will dedicate to tips to write PHP code that is easier to maintain, review, refactor,... These tips may be applied for other languages but are mainly focused on PHP.

The first one could be entitled as "return as soon as possible"™. It may be summarized as changing:

if ($conditionToPerform) {
    
perform();
} else {
    return 
false;
}

into:

if (!$conditionToPerform) {
    return 
false;
}
perform();

The benefit of writing code this way may not be obvious, but take a closer look at the next implementations of the setPassword() function of an imaginary User class:
Update: please, don't focus on the questionable usage of the exceptions here, the goal is to provide different behaviors that exit from the function. Returns has to be understood as a family containing: return, throw, trigger_error, die, exit,...

function setPassword($password$oldPassword) {
    if (
$password === $oldPassword) {
        return;
    }
    if (!
$this->checkPassword($oldPassword)) {
        throw new 
Exception('Wrong password.');
    }
    if (
strlen($password) < 8) {
        throw new 
Exception('Password too short.');
    }
    if (empty(
$password)) {
        throw new 
Exception('Empty password not permitted.');
    }
    
$this->password $password;
    
$this->save
();
}

compared to:

function setPassword($password$oldPassword) {
    if (
$password !== $oldPassword) {
        if (
$this->checkPassword($oldPassword)) {
            if (
strlen($password) >= 8) {
                if (!empty(
$password)) {
                    
$this->password $password;
                    
$this->save();
                } else {
                    throw new 
Exception('Empty password not permitted.');
                }
            } else {
                throw new 
Exception('Password too short.');
            }
        } else {
            throw new 
Exception('Wrong password.');
        }
    }
}

From this two implementations, we can see several benefits of the first one:
  • it is easier to read
  • reordering conditions is easier because they are not nested
  • the link between a condition and the corresponding Exception is more obvious
  • keep the indentation lower, which makes life easier with the limit of 80 chars/line
  • less line are changed across revisions which decrease the number of SVN conflicts possible and easier the code review. Take a look at the following pictures showing the same change requests (doing nothing if old and new password are the same + checking that password contains 8+ chars) implemented with the two styles:

Using "Return as soon as possible"™


Using nested conditions

Sunday, June 8, 2008

Apache as an MVC controller

My colleagues know I am not a big fan of PHP frameworks, this is probably why I somewhat agree with the no-framework PHP MVC framework of Rasmus Lerdorf or with the idea of Akash Mehta thinking, to an extent, that PHP IS a framework. Keeping this global picture in mind, let me show you how Apache could take the C of MVC.

Because not mixing the business logic and the presentation is always a good idea, this leads up to have two types of PHP files: those with business logic only (the M of MVC), whether it's OOP or procedural programming, and those that are just templates containing mostly HTML (the V of MVC).

Mapping URLs to actions/views is the job of Apache, by default, it will look at your URL and make the match with the corresponding script on the filesystem. This is why I consider Apache as a controller.

Nice URL

One of the goal of a web controller is also to provide nice URL's to your application, I will present you here two methods to achieve this with Apache exclusively.

Method #1

It is quiet common Apache being configured with

DirectoryIndex index.php
Knowing this you can architect your directories with the same structure as your URLs always with an index.php file handling the request. Let's take the example of a web project management application having a dedicated page for projects and users. Your URLs might be:
  • http://project-management/projects/?id=xxx
  • http://project-management/projects/remove/?id=xxx
  • http://project-management/users/?id=xxx
  • http://project-management/users/add/
Handling this could be done using the filesystem layout as shown on the following picture:

Method #2
Another method, which may provide you even nicer URLs, relies on Apache's mod_rewrite. Let's change our URLs a little bit:
  • http://project-management/projects/xxx
  • http://project-management/projects/remove/xxx
  • http://project-management/users/xxx
  • http://project-management/users/add/
To handle this, I use a flatter filesystem layout: with the following rewrite rules:

RewriteEngine On

# Preventing access to php files directly
RewriteRule \.php$ /NotFound [L]

# Simple URL mapping
RewriteRule ^/$ /index.php [L]
RewriteRule ^/users/$ /ListUsers.php [L]
RewriteRule ^/projects/$ /ListProjects.php [L]
RewriteRule ^/users/add/$ /AddUser.php [L]
RewriteRule ^/projects/add/$ /AddProject.php [L]

# URL mapping with captured IDs
RewriteRule ^/users/([0-9]+)$ /ViewUser.php?id=$1 [L]
RewriteRule ^/projects/([0-9]+)$ /ViewProject.php?id=$1 [L]
RewriteRule ^/users/remove/([0-9]+)$ /RemoveUser.php?id=$1 [L]
RewriteRule ^/projects/remove/([0-9]+)$ /RemoveProject.php?id=$1 [L]

The first thing done is preventing direct access to PHP files, this is not mandatory but it adds some more security. The only way to reach the desired script is to match strictly the regular expression making some input filtering at the same time. Then comes the real and interesting rewrite rules. First ones are very simple mapping while the 4 last ones takes care of extracting a numerical ID from the URL and passing it to PHP as a $_GET parameter.

What do you think about such approach? Please, leave some comments :)

Monday, May 26, 2008

Hierarchical data in MySQL (and other RDBMS)

Introduction

There are lot of cases we want to store hierarchical data into relational database instead of hierarchical ones like XML databases. Several approaches exist and are already well explained, the most well known are the adjacency list and the nested set models. After briefly introducing those models I will present an extension to the first one making it much more usable. This extension has already been presented by Joe Celko under the name Path enumeration

The adjacency list model

The most common way to store such data is using the adjacency list model as introduced by former IBM fellow Edgar F. Codd (the father of relational database theory):

idnameboss
1AnneNULL
2Bernard1
3Charlie1
4Delphine3
5Elodie3
6Fanny3
7Georges5

Such model respects fully the relational idea based on primary and foreign keys, but this system shows his limit when we have to retrieve the full path to a node: "Anne > Charlie > Elodie > Georges" or when we need all people working below Charlie. Such question typically requires recursion with many queries which may become very slow:

-- Path to Georges?
SELECT * FROM people WHERE id = 7; -- boss = 5
SELECT * FROM people WHERE id = 5; -- boss = 3
SELECT * FROM people WHERE id = 3; -- boss = 1
SELECT * FROM people WHERE id = 1; -- boss = NULL (STOP)

-- People under Charlie?
SELECT * FROM people WHERE boss IN (3); -- id = 4,5,6
SELECT * FROM people WHERE boss IN (4,5,6); -- id = 7
SELECT * FROM people WHERE boss IN (7); -- no results (STOP)

The nested set model

The second popular approach is to make use of the depth first traversal algorithm to assign a left and right number to any node of the tree:

idnamelftrgt
1Anne114
2Bernard23
3Charlie413
4Delphine56
5Elodie710
6Fanny1112
7Georges89

This model is algorithmically beautiful! It solves in an elegant way the problem of running multiple queries to retrieve hierarchical information:

-- Path to Georges?
SELECT * FROM people WHERE 8 BETWEEN lft AND rgt ORDER BY lft;

-- People under Charlie?
SELECT * FROM people WHERE lft BETWEEN 4 AND 13 ORDER BY lft;

While this model is very good at retrieving hierarchical information, it is somewhat more complex and less competitive at updating the hierarchy (inserting, moving or removing nodes) since it requires to update several records even if it is feasible in one query. Not to mention that this model is not relational at all and does not prevent through integrity constraints the accidental removal of a boss! However, this is easily circumvented by adding the boss column and then mixing both models.

Path enumeration model

The path enumeration model is based on the adjacency list one, the idea is quite simple: add a column materializing the full path (which is unique) to your node.

idnamebosspath
1AnneNULL/1/
2Bernard1/1/2/
3Charlie1/1/3/
4Delphine3/1/3/4/
5Elodie3/1/3/5/
6Fanny3/1/3/6/
7Georges5/1/3/5/7/

The path is always computed by taking the path of the parent node concatenated with "<ID>/". Retrieving the path to Georges or people working for Charlie is a kid's game:

-- Path to Georges?
SELECT path FROM people WHERE id = 7; -- path = /1/3/5/7/
SELECT * FROM people WHERE id IN (1,3,5,7) ORDER BY path;

-- People under Charlie?
SELECT * FROM people WHERE path LIKE '/1/3/%' ORDER BY path;

To benefit from all the speed of this solution, you should have a UNIQUE KEY on your path column. Unfortunately it isn't (yet?) possible to know the assigned auto-increment ID inside an insert-trigger using MySQL, it is then mandatory to work in two phases while inserting a record. A first solution is to insert dummy data in the path while taking care of the UNIQUE KEY constraint and then updating the record using LAST_INSERT_ID(). My preference goes to using another table (people_tree) with a 1..1 relation. Here is an example:

-- Adding Helena below Bernard
INSERT INTO people (name, boss) VALUES ('Helena', 2);
INSERT INTO people_tree
SELECT p.id, CONCAT(pt.path, p.id, '/') FROM people p JOIN people_tree pt ON p.boss = pt.id WHERE p.id = LAST_INSERT_ID();

Of course, this complexity may be hidden in a stored procedure.

Monday, December 3, 2007

Templatize your Apache configuration with mod_macro

Background

In a previous post I presented a way to clean up your Apache's configuration by putting your sites' configuration apart from your httpd.conf. In this one I will show you how to build a scheme to easier the maintenance of your Apache related configuration by avoiding some nasty copy/paste.

Current situation

Current apache structure

Let's analyze the structure of the current /etc/apache2/ directory displayed in the picture on the right:

  • httpd.conf is the single entry point for Apache's configuration, it contains the bare minimum configuration needed for the server's instance.
  • modules.d is the directory containing all modules and features configuration files, it is Gentoo's equivalent to handle the mods-enabled directory of Debian and derivatives. Those files are included from the httpd.conf with:
    Include /etc/apache2/modules.d/*.conf
  • vhosts.d is the directory containing all virtual host configuration files as explained in my previous post. This one is the equivalent of sites-enabled. Those files are included the same way than modules.d with:
    Include /etc/apache2/vhosts.d/*.conf

Lets have the configuration of www.example.org, wiki.example.org and forum.example.org all hosted by the same Apache instance.

Content of vhosts.d/www.example.org:

<VirtualHost *:80>
    ServerName www.example.org
    DocumentRoot /var/www/www.example.org

    ErrorLog /var/log/apache2/www.example.org/error_log
    CustomLog /var/log/apache2/www.example.org/access_log common
    php_admin_value error_log "/var/log/apache2/www.example.org/php_error_log"
</VirtualHost>

Content of vhosts.d/wiki.example.org:

<VirtualHost *:80>
    ServerName wiki.example.org
    DocumentRoot /var/www/wiki.example.org

    ErrorLog /var/log/apache2/wiki.example.org/error_log
    CustomLog /var/log/apache2/wiki.example.org/access_log common
    php_admin_value error_log "/var/log/apache2/wiki.example.org/php_error_log"
</VirtualHost>

Content of vhosts.d/forum.example.org:

<VirtualHost *:80>
    ServerName forum.example.org
    DocumentRoot /var/www/forum.example.org

    ErrorLog /var/log/apache2/forum.example.org/error_log
    CustomLog /var/log/apache2/forum.example.org/access_log common
    php_admin_value error_log "/var/log/apache2/forum.example.org/php_error_log"
</VirtualHost>

We may notice that strong conventions are used:

  1. Document root is located in /var/www/$FullyQualifiedDomainName
  2. All logs are stored inside /var/log/apache2/$FullyQualifiedDomainName
  3. Only the $FullyQualifiedDomainName differs from one configuration to another.

mod_macro in action

Because the configuration of the virtual hosts follow all the same rules, this is a perfect candidate for Fabien COELHO's mod_macro. This Apache module lets you define and use macros within Apache runtime configuration files. Just take a look at the following macro:

# Macro definition for a generic virtual host
<Macro VHost $fqdn>
    <VirtualHost *:80>
        ServerName $fqdn
        DocumentRoot /var/www/$fqdn

        ErrorLog /var/log/apache2/$fqdn/error_log
        CustomLog /var/log/apache2/$fqdn/access_log common
        php_admin_value error_log "/var/log/apache2/$fqdn/php_error_log"
    </VirtualHost>
</Macro>

Here is how the content of my virtual hosts configuration:

Content of vhosts.d/www.example.org:

Use VHost www.example.org

Content of vhosts.d/wiki.example.org:

Use VHost wiki.example.org

Content of vhosts.d/forum.example.org:

Use VHost forum.example.org

While this solution is just perfect in case every virtual hosts should be configured exactly the same way (in that case you may be interested in mod_vhost_alias) it doesn't provide much flexibility to modify a specific virtual host afterwards even temporarily. To overcome to this shortcoming, here is my final structure:Updated Apache structure

  • an additional macros.d to holds macros definitions into .conf files loaded with:
    Include /etc/apache2/macros.d/*.conf
  • a home made VirtualHost definition made in two parts:
    # Macro definition for a generic virtual host
    <Macro BeginVHost $fqdn>
        <VirtualHost *:80>
            ServerName $fqdn
            DocumentRoot /var/www/$fqdn

            ErrorLog /var/log/apache2/$fqdn/error_log
            CustomLog /var/log/apache2/$fqdn/access_log common
            php_admin_value error_log "/var/log/apache2/$fqdn/php_error_log"
    </Macro>
    <Macro EndVHost>
        </VirtualHost>
    </Macro>
  • virtual hosts defined with:
    Use BeginVHost www.example.org
        # Define specific virtual host configuration here.
        # Example:
        # Use ZendFramework

    Use EndVHost

Conclusions

This post doesn't attempt to state the best way to manage tons of virtual host configuration, for each environment, there are different considerations to be taken and there's no perfect way. The main reason I wrote this post was to share the lessons learned with mod_macro and Gentoo's way to organize things and maybe helping others to improve their Apache configuration.

Sunday, November 18, 2007

Modularize your apache configuration

First symptoms

The very first time I had to modify something into Apache's configuration, the documentation tells me I had to do so in a file named httpd.conf. With the time, this file became more and more unmanageable: tons of unsorted VirtualHost and Directory blocks mixed with custom changes and Linux distribution specific configuration.

First aid

Everything became cleaner when I modularized the configuration into separate files thanks to the Include directive where every virtual hosts were defined into separate files. The only change made to the httpd.conf was to append:

Include /etc/apache2/vhosts.d/*.conf

Every sites can then be configured into their own vhosts.d/$FullyQualifiedDomainName.conf file.

Example with vhosts.d/wiki.example.org:

<VirtualHost *:80>
    ServerName wiki.example.org
    DocumentRoot /var/www/wiki.example.org

    <Directory /var/www/wiki.example.org>
        AllowOverride None
    </Directory>

    ErrorLog /var/log/apache2/wiki.example.org/error_log
    CustomLog /var/log/apache2/wiki.example.org/access_log common
    php_admin_value error_log "/var/log/apache2/wiki.example.org/php_error_log"
</VirtualHost>

Health check

After this reorganization I was up to disable a site simply by renaming the related .conf file without editing it, every virtual hosts had their own file and, the most important IMO, sites configuration were not mixed with the rest! This is how Gentoo Linux organize its apache's configuration by default. My /etc/apache2 directory now looks like the image on the right.

Thursday, September 13, 2007

Building dynamic SQL queries an elegant way

Building dynamic SQL queries — which is very common to handle search forms — is most of the time made in programming languages by concatenating strings this insecure way:

<?php
require 'connect.php';

$firstname 'Patrick';
$lastname 'Allaert';

$query 'SELECT * FROM users WHERE 1';

if (!empty(
$firstname)) {
    
$query .= " AND firstname = '$firstname'";
}

if (!empty(
$lastname)) {
    
$query .= " AND lastname = '$lastname'";
}

foreach (
$db->query($queryPDO::FETCH_ASSOC) as $row) {
    
print_r($row);
}
?>


Excluding the fact that this code is vulnerable to SQL injection, which is avoided using either proper escaping or prepared statement, we have to admit that appending " WHERE 1" as basic condition and prepending all conditions with " AND " looks more like a hat trick than a proper way of coding although we are used to see this.

A proper but still insecure version of this script would be:

<?php
require 'connect.php';

$firstname 'Patrick';
$lastname 'Allaert';

$query 'SELECT * FROM users';

$cond = array();

if (!empty(
$firstname)) {
    
$cond[] = "firstname = '$firstname'";
}

if (!empty(
$lastname)) {
    
$cond[] = "lastname = '$lastname'";
}

if (
count($cond)) {
    
$query .= ' WHERE ' implode(' AND '$cond);
}

foreach (
$db->query($queryPDO::FETCH_ASSOC) as $row) {
    
print_r($row);
}
?>


In this last version, we made use of the implode function to glue all the conditions together in the case at least one condition is defined. To combine this with security, the next step is to use prepared statement:

<?php
require 'connect.php';

$firstname 'Patrick';
$lastname 'Allaert';

$query 'SELECT * FROM users';

$cond = array();
$params = array();

if (!empty(
$firstname)) {
    
$cond[] = "firstname = ?";
    
$params[] = $firstname;
}

if (!empty(
$lastname)) {
    
$cond[] = "lastname = ?";
    
$params[] = $lastname;
}

if (
count($cond)) {
    
$query .= ' WHERE ' implode(' AND '$cond);
}

$stmt $db->prepare($query);
$stmt->execute($params);

foreach (
$stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
    
print_r($row);
}
?>