This is the text describing PHP standards. refs #SDLC-4
Table of Contents
- Table of Contents
- Introduction
- Object-oriented programming (OOP)
- Model-view-controller (MVC)
- Frameworks
- Naming Convention
- Syntax
- Code Commenting
- Code Formatting
- Configuration File
- **Please Note:
- The usage of INI files is discouraged, as they contain sensitive information such as passwords/passphrases. If, however, you would like to construct an INI file without the sensitive information, please follow the following standards.
- If you are using INI files and the INI file(s) are under webroot (e.g. /apps/your_app_name/…), include a blank index.html or index.php file in a directory which contains your INI file
- Project Setup (File Structure)
- Security
- Never trust user-entered data
- Data integrity
- Borrowed code
- Anti-patterns
- References
- Suggested Resources
- Appendix A – Frameworks Evaluation – (IN PROGRESS)
- Evaluation Criteria
- Evaluations
- CakePHP:
- Symfony: Goods Deliverable?
- Appendix B – Security Anti-Patterns
- header(‘Location: …’)
- File inclusion
- Appendix C – Performance Anti-Patterns
- SQL Recursion and Iteration
Introduction
This document contains programming standards, best practices, and anti-patterns for PHP website development. Although we can not envision every potential project, scenario, or problem, this document hopes to tackle the most important and encompassing aspects of PHP development. We aspire to produce more secure applications which perform well and scale with user needs. We will make our applications easy to review and maintain through the use of standard syntax and common patterns.
Object-oriented programming (OOP)
Object-oriented programming allows function grouping and variable scoping which improves application security. Grouping functions in a logical way makes code easier to debug and maintain. While this standard does not require OOP, we strongly recommend OOP as a best practice.
Model-view-controller (MVC)
One style of object-oriented programming, Model-View-Controller (MVC), defines three types of classes:
- Model, a type of file that stores functions for data interaction (e.g., with the database, flat files, off-site APIs, etc.);
- View, a type of file that stores a template for the outcome of a page;
- Controller, a type of file that stores functions for handling the browser request, deciding which model to use, querying it, manipulating the received data, and passing it to the view.
GUIDELINE: Most projects should use MVC.
NOTE:
In most MVC-style websites programmed in PHP, the application handles views as a single class which takes a template file as a parameter, rather than a separate class for every view. We recommended this conciseness pattern.
Note
In addition to the three main types (model, view, controller) there may be other types of classes, such as a “helper” class, which stores functions accessible across multiple, unrelated sections of the website.
This diagram demonstrates how a request would propagate through a website:
Another request on the same example website:
In fact, nearly all modern PHP frameworks use MVC.
Frameworks
Before starting a project using a PHP framework, evaluate the pros and cons. A developer can significantly reduce their development time as long as they chose the right framework to fit the business process.
Things to consider when evaluating a framework:
- Hooks/Scaffolding for JSON RPC
- Support for testing
- Uses MVC design pattern
- Scalable
- Active online communities
- Clear, complete documentation
- Object-Oriented
- Template usage
- Easy integration with AJAX
- Supports JavaScript libraries (such as jQuery)
See “Appendix A” for evaluations of specific frameworks.
Naming Convention
Convention For |
Convention |
Example |
---|---|---|
Class Name |
|
|
Class Variables and Methods (Public) |
|
|
Class Variables and Methods (Private)* |
|
|
Class Variables and Methods (Protected)** |
|
|
Constants |
|
|
—-
*Only the parent classe can access private members.
**Parent class and extended classes from the parent may access protected members.
Syntax
Code Commenting
- Use the phpDoc specification
- Precede every class and function with a comment block
- Always comment code
Type of commenting in PHP:
// This is a comment |
public function sampleFn( $param /* String */ , $myvar = FALSE /* Boolean */ ) { if ( $myvar ) { echo 'sample ' . $param ; } } |
/* This is a multi-line comment; this should describe what is happening, in a chunk. */ |
All code files should start with the following block-comment:
/** * Content Layout * * This file contains the template layout * * @author Rushikumar Bhatt * @version 1.0 * @package sample */ |
Other (standard) phpDoc tags include:
- @access
- @copyright
- @deprecated
- @example
- @ignore
- @internal
- @link
- @see
- @since
- @tutorial
- inline {@internal}
- inline {@inheritdoc}
- inline {@link}
Fore more information, please refer to the phpDocumentor tags page.
Putting it into Practice:
Use comments to describe application state. For example, in a controller with a function to display all workout groups for a selected team, this comment will help the developers debug and maintain the application:
1
2
3
4
5
6
7
8
9
|
/** * Displays all WorkoutGroups for the selected Team. * @method index * @version 1.0 **/ function index() { $data = $this ->WorkoutGroup->getAllByTeam( $this ->Session->read( 'selected_team.id' )); $this ->set( 'data' , $data ); } |
Code Formatting
- Do not obfuscate code.
- Indent code and use white space for readability
- Adhere to one of the four main format styles: K&R Style (Kernel Style), Allman Style (BSD Style), Whitesmiths Style, and GNU Style
- Use an IDE (integrated development environment) such as DreamWeaver, Tomcat, or Netbeans to help format code consistently
PHP beautifiers such as PHPFormatter and PHP Beautifier may help when dealing with poorly styled code.
K&R Style (Kernel Style) |
Allman Style (BSD Style) |
Whitesmiths Style |
GNU Style |
---|---|---|---|
Explanation: Named after Kernighan & Ritchiebecause of the formatting used in their examples.Also called kernel style because the Unix kernel uses this format and the “One True Brace Style” (abbrev. 1TBS). K&R style indents the body of a code block by one tab (typically four spaces although eight spaces appears in examples written in C). |
Explanation: Named for Eric Allman who wrote a lot of the BSD utilities using this style.Resembles normal indent style in Pascal and Algol. It is the only style other than K&R in widespread use among Java programmers. This style uses a basic indent per level shown below of eight spaces, although C++ and Java programmers may prefer four (or sometimes three). |
Explanation: Popularized by the examples that came with Whitesmiths C, an early commercial C compiler.This style uses a basic indent per level shown below of eight spaces but some programmers use four spaces. |
Explanation: Used throughout GNU EMACSand the Free Software Foundation code.GNU always indents four spaces per level, with “{” and “}” halfway between the outer and inner indent levels. |
Example of K&R Style: |
1
2
3
|
if ([cond]) { [body] } |
Example of Allman Style: |
1
2
3
4
|
if ([cond]) { [body] } |
Example of Whitesmiths Style: |
1
2
3
4
|
if ([cond]) { [body] } |
Example of GNU Style: |
1
2
3
4
|
if ([cond]) { [body] } |
Note the difference between the two:
1
2
3
4
5
|
function getFirstName( $id ){ if ( $loggedIn ){ return $this ->name; } } |
1
2
3
4
|
function getFirstName( $id ){ if ( $loggedIn ) return $this ->name; } |
Group Consensus
Icon
The PHP Standards Group recommends the K&R Style.
Configuration File
**Please Note:
-
The usage of INI files is discouraged, as they contain sensitive information such as passwords/passphrases. If, however, you would like to construct an INI file without the sensitive information, please follow the following standards.
-
If you are using INI files and the INI file(s) are under webroot (e.g. /apps/your_app_name/…), include a blank index.html or index.php file in a directory which contains your INI file
To prevent the need for unit testing or regression testing for simple variable changes, use a configuration file (.ini). Since many of the variables needed for environment setup and control are sensitive (e.g., database connection strings, user names), do not include .ini files in your application build or source control systems. Name the configuration files with the target environment name to allow the application to pick the right file based on the current runtime environment.
Example of a Configuration file
1
2
3
4
5
6
7
8
9
|
; Comments start with ';' , as in php.ini [debug] enable = true [database] type = OracleSQL server = //cbatest2.uits.uconn.edu:1521/FAMIS.cbatest2.uits.uconn.edu user = my_secret_user |
A word of caution!
Icon
The following characters must not be used for Key/Value Pairs:
?{}|&~![()^”
The aforementioned characters have special meanings, when used in value.
/** * Configuration * * This file contains the Configuration class * * @author Paul Grenier * @version 1.0 * @package DAO * @example Configuration::getInstance()->database_type; */ class Configuration { static private $_instance = NULL; private $_iniSettings ; protected function __construct() { //trick to getting relative path $path = str_replace ( '//' , '/' , dirname( __FILE__ ). '/' ). '../Config/' ; $file = $_SERVER [ 'SERVER_NAME' ] . '.ini' ; if ( file_exists ( $path . $file )) { $this ->_iniSettings = parse_ini_file ( $path . $file , true); } else { throw new Exception( "Config file $file not found at $path." ); } } public function __get( $name ) { $arr = explode ( '_' , $name , 2); $ini = $this ->_iniSettings; $section = $arr [0]; $item = $arr [1]; if ( array_key_exists ( $section , $ini )) { if ( $item && array_key_exists ( $item , $ini [ $section ])) { return $ini [ $section ][ $item ]; } return $ini [ $section ]; } return null; } public function __destruct() { } public static function getInstance() { if (self:: $_instance == NULL) { self:: $_instance = new Configuration(); } return self:: $_instance ; } public function __clone() { throw new Exception( 'Clone is not allowed.' ); } } ?> |
Project Setup (File Structure)
Use an organized file structure to lay out projects (example below):
Level |
Type of Level/Directory |
Type of files |
---|---|---|
/ | ROOT | This directory contains all of the files called directly by the browser. |
/include | INCLUDES | This directory contains all of the files to be included into other files. Also, this directory holds other sub-directories, such as “javascripts.” |
/include/javascripts | SCRIPTS | This directory contains JavaScript files and resides inside the “include” directory. |
/include/images OR /images |
IMAGES | This directory contains all image files. This can either go under the include directory or at the root (/) level. |
Security
Never trust user-entered data
Assume all data is user-entered data and escape data immediately when it needs to be escaped, not earlier. This avoids both unescaped data and redundant escaping (e.g., '
).
For example, when escaping data for a SQL query, escape everything, and escape during the concatenation, not before:
<?php $example = $_GET [ 'example' ]; if ( $_GET [ 'example2' ] == true) { $example2 = "foo" ; } else { $example2 = "bar" ; } $query = "SELECT 'col1', 'col2' FROM 'tbl' WHERE 'col3' = '" .mysql_real_escape_string( $example ). "' AND 'col4' = '" .mysql_real_escape_string( $example2 ). "'" ; ?> |
Note
Icon
Some frameworks or database functions using bind variables may supply a separate means of securing input.{{}}
When echoing a string not containing HTML, escape it inside the echo
parameter, not before you store it:
<?php $example = "This is an example string." ; echo htmlspecialchars( $example ); ?> |
Data integrity
Do not rely solely on client-side (JavaScript) validation as users can easily circumvent this. Include a server-side version of any data validation.
Also, to ensure data integrity, consider including constraints in the database (i.e., foreign keys, unique constraints) to reduce the chances that invalid data reaches your application through others means (e.g., direct database manipulation, improper code).
Borrowed code
Many developers take advantage of the abundance of free and open-source code on the internet. However, these scripts can create security vulnerabilities because of a) the large number of people working on them; b) the availability of the code to anyone who wishes to view it; and c) the sometimes heavy use of these scripts on many sites, resulting in more incentive for hackers to find holes in them.
Do not waste time reinventing the wheel (as often in-house renditions can have many of the same security issues). But stay alert and always watch for any released security vulnerabilities for the code you use and always patch your code with the supplied updates or patch it yourself and share your changes with the code community.
Anti-patterns
See Appendix B for security anti-patterns.
References
- Naming Conventions – Pear.php.net
- Net Tuts+ – 30+ PHP Best Practices for Beginners
- Stackoverflow – Symfony vs CakePHP
- PlentyofCode – Why you should use Symfony?
- CakePHP – What is CakePHP? Why Use it?
- PHP Framworks – Cake PHP vs Code Igniter vs Zend framework
- phpDoc
- phpDocumentor tags
Suggested Resources
- FishEye
- Fisheye nicely ties into an SVN or CVS (or Perforce, Git, or ClearCase, for that matter).
Appendix A – Frameworks Evaluation – (IN PROGRESS)
This Appendix describes evaluations performed for the listed/mentioned frameworks. Evaluating the growing landscape of ever-evolving frameworks takes time. Please check back often for updates.
Evaluation Criteria
The test process for each framework includes:
- Download the Framework
- Install it on a development machine
- Configure the framework so to use CAS
- Develop a simple application
- Configure the framework to communicating with a database (Oracle is preferred)
- Record results at each step
- This may include information such as the time it took to do a task
For “developing a simple application” task, we used a “ThoughtBoard” application with the following requirements:
- Application shall be CASified
- Application shall be able to take an input by the user, “thought”, and save it in the database
- Application shall be able to display all the inputs, “thoughts”, of the users – retrieving it from the database
- Application shall be able to edit a given input (“thought”)
- Application shall be able to delete a given input (“thought”)
- Application shall be able to search through inputs (“thoughts”) for a specified query string
The entity-relation diagram for the “ThoughtBoard” database looks as follows:
Evaluations
To date, we have evaluated frameworks: CakePHP and Symfony. We plan to evaluate six more frameworks: CodeIgniter, Solar, Yii, Kohana, Akelos, and Agavi.
We used CakePHP for one of our projects and as a direct result of that experience will not recommend it. However, CakePHP has some benefits.
If you are a fan of PHP CLI (PHP Command Line Interface), configuration via YAML files, using ORM, like RoR, you may like Symfony. Symfony’s strong documentation, vibrant user-community, and great examples will encourage adoption.
CakePHP:
Pros |
Cons |
---|---|
Active, friendly community | Poor Documentation |
MVC Architecture | No Template Support |
Code Generation | No modules integration |
Integrated CRUD for database Interaction | Steep learning curve due to poor documentation. |
Data Sanatization | |
Localization | |
ORM Built-in | |
Fast / Flexible templating | |
Application Scaffolding | |
Smaller learning Curve |
Symfony: Goods Deliverable?
Pros |
Cons |
---|---|
Good Documentation | PHP CLI – most developers will NOT have access to all three environments/boxes (DEV / TEST /AND PROD) |
MVC Architecture | A lot of configuration, before seeing results. – Populating a YAML files, for configuration |
Code Generation | |
Data Sanatization | |
Localization | |
ORM Choice (Propel or Doctrine) | |
Highly Modular** – Many of the components work on their own |
|
Model definition with YAML or PHP – If you don’t like configuration files, stay away from YAML |
|
Symfony CLI is very well documented | |
A lot of similarities with RoR | |
Symfony admin generator! |
Appendix B – Security Anti-Patterns
This section outlines some common patterns that leave security holes in website code and provides an explanation and fix for the code.
header('Location: ...')
<?php if ( $notAuthorized ) { header( 'Location: denied.php' ); } echo 'Sensitive data' ; ?> |
This code would supposedly direct any unauthorized users away from the page. However, clients are not required to listen to header()
commands.Therefore, if a malicious client chose to ignore the headers, they would still be able to access the sensitive data.
The proper fix would be as follows:
<?php if ( $notAuthorized ) { header( 'Location: denied.php' ); exit (); } echo 'Sensitive data' ; ?> |
This way, the sensitive data would never be sent if the user is not authorized.
File inclusion
Often a pattern used for routing a whole website through a single PHP page is as follows:
<?php include ( $_GET [ 'page' ]. '.php' ); ?> |
However, if $_GET['page']
were set to a URL, or a path such as ‘admin/deleteEverything.php’ it could open vulnerabilities.
Either use a proper router, or the following code to ensure sanity in your inclusion path:
<?php function validFilename( $n ) { return ( substr ( $n , 0, 1) != "." && strpos ( $n , '/' ) === FALSE && strpos ( $n , '' ) === FALSE); } $path = $_GET [ 'path' ]. '.php' ; if (validFilename( $path )) { include ( $path ); } ?> |
Appendix C – Performance Anti-Patterns
SQL Recursion and Iteration
Try to limit database calls on a page. Do not place database calls inside functions or methods that will be invoked recursively or iteratively. Even when executed in a single transaction with bind variables, this pattern will not scale well. In nearly every case, you can solve this type of problem by writing better SQL.
<?php /* DON'T DO THIS */ function printUserName( $id ) { //SELECT first_name, last_name FROM users WHERE id = $id; echo $first_name . ' ' . $last_name ; } foreach ( $users as $user ) { printUserName( $user ->id); } /* INSTEAD DO THIS */ function getUserNames( $idArray = NULL /* Array */ ) { if ( $idArray && is_array ( $id_array ) { $idList = implode( ', ' , $idArray ); $filter = "WHERE id IN ($idList)" ; } else { $filter = '' ; } //SELECT id, first_name, last_name FROM users $filter; return $results ; } $userNames = getUserNames( $users ); foreach ( $userNames as $user ) { echo $user ->first_name . ' ' . $user ->last_name; } ?> |
In this example, we made a more robust function that can return all user names or just ones from a list (array). We then iterate over the database results rather than call the database iteratively.
—
EoF – Last Updated 07/31/2012