Skip to main content

PHP Best Practices

This guide will give you solutions to common PHP design problems. It also provides a sketch of an application layout that I developed during the implementation of some projects.
php.ini quirks
Some settings in the php.ini control how PHP interpretes your scripts. This can lead to unexpected behaviour when moving your application from development to the productive environment. The following measures reduce dependency of your code on php.ini settings.
short_open_tagAlways use the long PHP tags: php
echo "hello world"; ?>
Do not use the echo shortcut .
asp_tagsDo not use ASP like tags: <% echo "hello world"; %>
gpc_magic_quotesI recommend that you include code in a global include file which is run before any $_GET or $_POST parameter or $_COOKIE is read. That code should check if the gpc_magic_quotes option is enabled and run all $_GET, $_POST and $_COOKIE values through the stripslashes function.
register_globalsNever rely on this option beeing set. Always access all GET, POST and COOKIE values through the 'superglobal' $_GET, $_POST and $_COOKIE variables. For convenience declare $PHP_SELF = $_SERVER['PHP_SELF']; in your global include file after the gpc_magic_quotes quirk.
File uploads:The maximum size of an uploaded file is determined by the following parameters:

  • file_uploads must be 1 (default)
  • memory_limit must be slightly larger than the post_max_size and upload_max_filesize
  • post_max_size must be large enough
  • upload_max_filesize must be large enough
Have one single configuration file
You should define all configuration parameters of your application in a single (include) file. This way you can easily exchange this file to reflect settings for your local development site, a test site and the customer's production environment. Common configuration parameters are:

  • database connection parameters
  • email addresses
  • options
  • debug and logging output switches
  • application constants
Keep an eye on the namespace
As PHP does not have a namespace facility like Java packages, you must be very careful when choosing names for your classes and functions.

  • Avoid functions outside classes whenever possible and feasible. Classes provide some extra namespace for the methods and variables that live inside them.
  • If you declare global functions use a prefix. Some examples are dao_factory(),db_getConnection(), text_parseDate() etc.
Use a database abstraction layer
In PHP there are no database-independent functions for database access apart from ODBC (which nobody uses on Linux). You should not use the PHP database functions directly because this makes it expensive when the database product changes. Your customer may move from MySQL to Oracle one day or you will need an XML database maybe. You never know. Moreover an abstraction layer can ease development as the PHP database functions are not very userfriendly.Use Value Objects (VO)
VOs are actually a J2EE pattern. It can easily be implemented in PHP. A value object corresponds directly to a C struct. It's a class that contains only member variables and no methods other than convenience methods (usually none). A VO corresponds to a business object. A VO typically corresponds directly to a database table. Naming the VO member variables equal to the database fields is a good idea. Do not forget the ID column.class Person {
var $id, $first_name, $last_name, $email;
}
Use Data Access Objects (DAO)
DAO is actually a J2EE pattern. It can easily be implemented in PHP and helps greatly in separating database access from the rest of your code. The DAOs form a thin layer. The DAO layer can be 'stacked' which helps for instance if you want to add DB caching later when tuning your application. You should have one DAO class for every VO class. Naming conventions are a good practice. class PersonDAO {
var $conn;
function PersonDAO(&$conn) {
$this->conn =& $conn;
}
function save(&$vo) {
if ($v->id == 0) {
$this->insert($vo);
} else {
$this->update($vo);
}
}
function get($id) {
#execute select statement
#create new vo and call getFromResult
#return vo
}
function delete(&$vo) {
#execute delete statement
#set id on vo to 0
}
#-- private functions
function getFromResult(&vo, $result) {
#fill vo from the database result set
}
function update(&$vo) {
#execute update statement here
}
function insert(&$vo) {
#generate id (from Oracle sequence or automatically)
#insert record into db
#set id on vo
}
}
A DAO typically implements the following methods:

  • save: inserts or updates a record
  • get: fetches a record
  • delete: removes a record
The DAO may define additional methods as required by your application's needs. The should only perform actions that require the database (maybe only for performance reasons) and can not be implemented in a different mannor. Examples: isUsed(), getTop($n), find($criteria).
The DAO should only implement basic select / insert / update operations on onetable. It must not contain the business logic. For example the PersonDAO should not contain code to send email to a person. For n-to-n relationships create a separate DAO (and even a VO if the relationships has additional properties) for the relation table.
Write a factory function that returns the proper DAO given the class name of a VO.Caching is a good idea here. function dao_getDAO($vo_class) {
$conn = db_conn('default'); #get a connection from the pool
switch ($vo_class) {
case "person": return new PersonDAO($conn);
case "newsletter": return new NewsletterDAO($conn);
...
}
}
Generate code
99% of the code for your VOs and DAOs can be generated automatically from your database schema when you use some naming conventions for your tables and columns. Having a generator script ready saves you time when you are likely to change the database schema during development. I successfully used a perl script to generate my VOs and DAOs for a project. Unfortunately I am not allowed to post it here.Business logic
Business logic directly reflects the use cases. The business logic deals with VOs, modifies them according to the business requirements and uses DAOs to access the persistence layer. The business logic classes should provide means to retrieve information about errors that occurred. class NewsletterLogic {
function NewsletterLogic() {
}
function subscribePerson(&$person) {
...
}
function unsubscribePerson(&$person) {
...
}
function sendNewsletter(&$newsletter) {
...
}
}
Page logic (Controller)
When a page is called, the page controller is run before any output is made. The controller's job is to transform the HTTP request into business objects, then call the approriate logic and prepare the objects used to display the response.The page logic performs the following steps:
1. The cmd request parameter is evaluated. 2. Based on the action other request parameters are evaluated.3. Value Objects (or a form object for more complex tasks) are created from the parameters.4. The objects are validated and the result is stored in an error array.5. The business logic is called with the Value Objects.6. Return status (error codes) from the business logic is evaluated.7. A redirect to another page is executed if necessary.8. All data needed to display the page is collected and made available to the page as variables of the controller. Do not use global variables.
Note: it is a good idea to have a utility function that returns a parameter that is sent via GET or POST respectivly and provide a default value if the parameter is missing. The page logic is the only non-HTML include file in the actual page! The page logic file must include all other include files used by the logic (see base.inc.php below). Use the require_once PHP command to include non-HTML files.class PageController {
var $person; #$person is used by the HTML page
var $errs;
function PageController() {
$action = Form::getParameter('cmd');
$this->person = new Person();
$this->errs = array();
if ($action == 'save') {
$this->parseForm();
if (!this->validate()) return;
NewsletterLogic::subscribe($this->person);
header('Location: confirmation.php');
exit;
}
}
function parseForm() {
$this->person->name = Form::getParameter('name');
$this->person->birthdate = Util::parseDate(Form::getParameter('birthdate');
...
}
function validate() {
if ($this->person->name == '') $this->errs['name'] = FORM_MISSING;
#FORM_MISSING is a constant
...
return (sizeof($this->errs) == 0);
}
}
Presentation Layer
The top level page will contain the actual HTML code. You may include HTML parts that you reuse across pages like the navigation etc. The page expects the page logic to prepare all business objects that it needs. It's a good idea to document the business objects needed at the top of the page.The page accesses properties of those business objects and formats them into HTML. <?php require_once('control/ctl_person.inc.php'); #the page controller
$c =& new PageController();
?>
<html>
<body>
<form action="<?php echo htmlspecialchars($PHP_SELF) ?>" method="POST">
<input type="hidden" name="cmd" value="save">
<input type="text" name="name"
value="<?php echo htmlspecialchars($c->person->name); ?>">
<button type="submit">Subscribe</button>
</form>
</body>
</html>
Localization
Localization is a problem. You must choose among a) duplicating pages b) removing all hardcoded strings from your HTML.
As I work in a design company I usually take approach a). Approach b) is not feasible as it makes the HTML very hard to read and nearly impossible to edit in a visual web editor like Dreamweaver. Dynamic content is hard enough to edit with Dreamweaver. Removing also all strings, makes the page look quite empty...
So finish the project in one language first. The copy the HTML pages that need translation. Use a naming convention like index_fr.php to designate the French version of the index page. Always use the ISO two letter language codes. Do not invent your own language codes.
To keep track of the language the user selected you must choose among a) storing the language setting in a session variable or cookieb) reading the preferred language (locale) from the HTTP headers the browser sends youc) appending the language to the URL of every link in your application
While a) seems a lot more easier than c) it may be subject to session timeout. Option b) should only be implemented as an extension to a) or c). Strings in a database must be localized too!Making your application location independent
PHP has problems in some situations when include files are nested and reside in different folders and it is unclear at which directory level the file will be included. One can solve this by using absolute path names or using $_SERVER['DOCUMENT_ROOT']as a starting point. However this makes your code location dependent - it will not run anymore if you move it down your directory structure one level. Of cource we do not like that.I have found a convenient solution to this problem. The toplevel page (the one that is called by the browser) needs to know the relative path to the application root directory. Unfortunately there is no such function in PHP and the webapp context concept is completely absent in PHP. So we can not automatically determine the application root reliably in all situations (It is *really* impossible. Don't even try. It's not worth the effort.)Let's define a global variable called $ROOT in an include file in every directory that contains toplevel pages. The include file (call it root.inc.php) must be included by the page logic before any other include files. Now you can use the $ROOT variable to reference include files with their exact path!
Sample:We have toplevel pages in /admin/pages/. The $ROOT variable must therefore be set to$ROOT = '../..';. The page logic included by pages in that folder would reference their include files like require_once("$ROOT/lib/base.inc.php");.
In my suggested folder outline (see below) we don't even need that, since all toplevel pages reside in the webapp root directory anyway. So the webapp root directory is always the current directory.Folder outline
I suggest you make one file per class and follow a naming convention. Make sure that all your include files end with .php to avoid disclosure of your code to malicious users, which is a major security problem. I suggest the following folder structure:

/Webapp root directory. Contains the pages that are actually called by the browser.
/lib/Contains base.inc.php and config.inc.php
/lib/common/Contains libraries and tools reusable for other projects, like your database abstraction classes.
/lib/model/Contains the Value Object classes
/lib/dao/Contains the DAO classes and the DAO factory
/lib/logic/Contains the business logic classes
/parts/Contains partial HTML that is included by pages
/control/Contains the page logic.
For larger applications you may want additional sub-directories for the individual parts (e.g. /admin/, /pub/) of your application to make the root directory a little lighter. Each of them would have their own control sub-directory.
Provide a base.inc.php file that includes (require_once) in the right order:

  • frequently used stuff (database layer) from /lib/common
  • the config include file
  • all classes from /lib/model
  • all classes from /lib/dao

Of course you will have additional directories for your images, uploaded files, ... etc.
php.ini quirks
Some settings in the php.ini control how PHP interpretes your scripts. This can lead to unexpected behaviour when moving your application from development to the productive environment. The following measures reduce dependency of your code on php.ini settings.
short_open_tag
Always use the long PHP tags: php
echo "hello world"; ?>
Do not use the echo shortcut .
asp_tags
Do not use ASP like tags: <% echo "hello world"; %>
gpc_magic_quotes
I recommend that you include code in a global include file which is run before any $_GET or $_POST parameter or $_COOKIE is read. That code should check if the gpc_magic_quotes option is enabled and run all $_GET, $_POST and $_COOKIE values through the stripslashes function.
register_globals
Never rely on this option beeing set. Always access all GET, POST and COOKIE values through the 'superglobal' $_GET, $_POST and $_COOKIE variables. For convenience declare $PHP_SELF = $_SERVER['PHP_SELF']; in your global include file after the gpc_magic_quotes quirk.
File uploads:
The maximum size of an uploaded file is determined by the following parameters:

  • file_uploads must be 1 (default)
  • memory_limit must be slightly larger than the post_max_size and upload_max_filesize
  • post_max_size must be large enough
  • upload_max_filesize must be large enough
Have one single configuration file
You should define all configuration parameters of your application in a single (include) file. This way you can easily exchange this file to reflect settings for your local development site, a test site and the customer's production environment. Common configuration parameters are:

  • database connection parameters
  • email addresses
  • options
  • debug and logging output switches
  • application constants
Keep an eye on the namespace
As PHP does not have a namespace facility like Java packages, you must be very careful when choosing names for your classes and functions.

  • Avoid functions outside classes whenever possible and feasible. Classes provide some extra namespace for the methods and variables that live inside them.
  • If you declare global functions use a prefix. Some examples are dao_factory(),db_getConnection(), text_parseDate() etc.
Use a database abstraction layer
In PHP there are no database-independent functions for database access apart from ODBC (which nobody uses on Linux). You should not use the PHP database functions directly because this makes it expensive when the database product changes. Your customer may move from MySQL to Oracle one day or you will need an XML database maybe. You never know. Moreover an abstraction layer can ease development as the PHP database functions are not very userfriendly.
Use Value Objects (VO)
VOs are actually a J2EE pattern. It can easily be implemented in PHP. A value object corresponds directly to a C struct. It's a class that contains only member variables and no methods other than convenience methods (usually none). A VO corresponds to a business object. A VO typically corresponds directly to a database table. Naming the VO member variables equal to the database fields is a good idea. Do not forget the ID column.
class Person {
var $id, $first_name, $last_name, $email;
}
Use Data Access Objects (DAO)
DAO is actually a J2EE pattern. It can easily be implemented in PHP and helps greatly in separating database access from the rest of your code. The DAOs form a thin layer. The DAO layer can be 'stacked' which helps for instance if you want to add DB caching later when tuning your application. You should have one DAO class for every VO class. Naming conventions are a good practice.
class PersonDAO {
var $conn;
function PersonDAO(&$conn) {
$this->conn =& $conn;
}
function save(&$vo) {
if ($v->id == 0) {
$this->insert($vo);
} else {
$this->update($vo);
}
}
function get($id) {
#execute select statement
#create new vo and call getFromResult
#return vo
}
function delete(&$vo) {
#execute delete statement
#set id on vo to 0
}
#-- private functions
function getFromResult(&vo, $result) {
#fill vo from the database result set
}
function update(&$vo) {
#execute update statement here
}
function insert(&$vo) {
#generate id (from Oracle sequence or automatically)
#insert record into db
#set id on vo
}
}
A DAO typically implements the following methods:

  • save: inserts or updates a record
  • get: fetches a record
  • delete: removes a record
The DAO may define additional methods as required by your application's needs. The should only perform actions that require the database (maybe only for performance reasons) and can not be implemented in a different mannor. Examples: isUsed(), getTop($n), find($criteria).
The DAO should only implement basic select / insert / update operations on onetable. It must not contain the business logic. For example the PersonDAO should not contain code to send email to a person. For n-to-n relationships create a separate DAO (and even a VO if the relationships has additional properties) for the relation table.
Write a factory function that returns the proper DAO given the class name of a VO.Caching is a good idea here.
function dao_getDAO($vo_class) {
$conn = db_conn('default'); #get a connection from the pool
switch ($vo_class) {
case "person": return new PersonDAO($conn);
case "newsletter": return new NewsletterDAO($conn);
...
}
}
Generate code
99% of the code for your VOs and DAOs can be generated automatically from your database schema when you use some naming conventions for your tables and columns. Having a generator script ready saves you time when you are likely to change the database schema during development. I successfully used a perl script to generate my VOs and DAOs for a project. Unfortunately I am not allowed to post it here.
Business logic
Business logic directly reflects the use cases. The business logic deals with VOs, modifies them according to the business requirements and uses DAOs to access the persistence layer. The business logic classes should provide means to retrieve information about errors that occurred.
class NewsletterLogic {
function NewsletterLogic() {
}
function subscribePerson(&$person) {
...
}
function unsubscribePerson(&$person) {
...
}
function sendNewsletter(&$newsletter) {
...
}
}
Page logic (Controller)
When a page is called, the page controller is run before any output is made. The controller's job is to transform the HTTP request into business objects, then call the approriate logic and prepare the objects used to display the response.
The page logic performs the following steps:
1. The cmd request parameter is evaluated. 
2. Based on the action other request parameters are evaluated.
3. Value Objects (or a form object for more complex tasks) are created from the parameters.
4. The objects are validated and the result is stored in an error array.
5. The business logic is called with the Value Objects.
6. Return status (error codes) from the business logic is evaluated.
7. A redirect to another page is executed if necessary.
8. All data needed to display the page is collected and made available to the page as variables of the controller. Do not use global variables.
Note: it is a good idea to have a utility function that returns a parameter that is sent via GET or POST respectivly and provide a default value if the parameter is missing. The page logic is the only non-HTML include file in the actual page! The page logic file must include all other include files used by the logic (see base.inc.php below). Use the require_once PHP command to include non-HTML files.
class PageController {
var $person; #$person is used by the HTML page
var $errs;
function PageController() {
$action = Form::getParameter('cmd');
$this->person = new Person();
$this->errs = array();
if ($action == 'save') {
$this->parseForm();
if (!this->validate()) return;
NewsletterLogic::subscribe($this->person);
header('Location: confirmation.php');
exit;
}
}
function parseForm() {
$this->person->name = Form::getParameter('name');
$this->person->birthdate = Util::parseDate(Form::getParameter('birthdate');
...
}
function validate() {
if ($this->person->name == '') $this->errs['name'] = FORM_MISSING;
#FORM_MISSING is a constant
...
return (sizeof($this->errs) == 0);
}
}
Presentation Layer
The top level page will contain the actual HTML code. You may include HTML parts that you reuse across pages like the navigation etc. The page expects the page logic to prepare all business objects that it needs. It's a good idea to document the business objects needed at the top of the page.
The page accesses properties of those business objects and formats them into HTML.
<?php require_once('control/ctl_person.inc.php'); #the page controller
$c =& new PageController();
?>
<html>
<body>

php
echo htmlspecialchars($PHP_SELF) ?>" method="POST">

value="php echo htmlspecialchars($c->person->name); ?>">




Localization
Localization is a problem. You must choose among 
a) duplicating pages 
b) removing all hardcoded strings from your HTML.
As I work in a design company I usually take approach a). Approach b) is not feasible as it makes the HTML very hard to read and nearly impossible to edit in a visual web editor like Dreamweaver. Dynamic content is hard enough to edit with Dreamweaver. Removing also all strings, makes the page look quite empty...
So finish the project in one language first. The copy the HTML pages that need translation. Use a naming convention like index_fr.php to designate the French version of the index page. Always use the ISO two letter language codes. Do not invent your own language codes.
To keep track of the language the user selected you must choose among 
a) storing the language setting in a session variable or cookie
b) reading the preferred language (locale) from the HTTP headers the browser sends you
c) appending the language to the URL of every link in your application
While a) seems a lot more easier than c) it may be subject to session timeout. Option b) should only be implemented as an extension to a) or c). 
Strings in a database must be localized too!
Making your application location independent
PHP has problems in some situations when include files are nested and reside in different folders and it is unclear at which directory level the file will be included. One can solve this by using absolute path names or using $_SERVER['DOCUMENT_ROOT']as a starting point. However this makes your code location dependent - it will not run anymore if you move it down your directory structure one level. Of cource we do not like that.
I have found a convenient solution to this problem. The toplevel page (the one that is called by the browser) needs to know the relative path to the application root directory. Unfortunately there is no such function in PHP and the webapp context concept is completely absent in PHP. So we can not automatically determine the application root reliably in all situations (It is *really* impossible. Don't even try. It's not worth the effort.)
Let's define a global variable called $ROOT in an include file in every directory that contains toplevel pages. The include file (call it root.inc.php) must be included by the page logic before any other include files. Now you can use the $ROOT variable to reference include files with their exact path!
Sample:
We have toplevel pages in /admin/pages/. The $ROOT variable must therefore be set to$ROOT = '../..';. The page logic included by pages in that folder would reference their include files like require_once("$ROOT/lib/base.inc.php");.
In my suggested folder outline (see below) we don't even need that, since all toplevel pages reside in the webapp root directory anyway. So the webapp root directory is always the current directory.
Folder outline
I suggest you make one file per class and follow a naming convention. Make sure that all your include files end with .php to avoid disclosure of your code to malicious users, which is a major security problem. I suggest the following folder structure:
/Webapp root directory. Contains the pages that are actually called by the browser.
/lib/Contains base.inc.php and config.inc.php
/lib/common/Contains libraries and tools reusable for other projects, like your database abstraction classes.
/lib/model/Contains the Value Object classes
/lib/dao/Contains the DAO classes and the DAO factory
/lib/logic/Contains the business logic classes
/parts/Contains partial HTML that is included by pages
/control/Contains the page logic.
For larger applications you may want additional sub-directories for the individual parts (e.g. /admin/, /pub/) of your application to make the root directory a little lighter. Each of them would have their own control sub-directory.
Provide a base.inc.php file that includes (require_once) in the right order:

  • frequently used stuff (database layer) from /lib/common
  • the config include file
  • all classes from /lib/model
  • all classes from /lib/dao

Of course you will have additional directories for your images, uploaded files, ... etc.
Original Article: http://www.odi.ch/prog/design/php/guide.php



Original article:This guide will give you solutions to common PHP design problems. It also provides a sketch of an application layout that I developed during the implementation of some projects.

Comments

Popular posts from this blog

Survey says: PHP passes Microsoft Active Server Pages

By JT Smith on June 11, 2002 (8:00:00 AM) With a faltering economy forcing companies to cut spending whenever possible, less expensive and freely available Open Source software solutions may be gaining in popularity. Those wanting proof can look no further to PHP taking the top server-side scripting spot in a recent Internet host survey. In April 2002, Netcraft's monthly Web server survey revealed that 24 percent, or around 9 million of the 37 million sites it surveyed, were using Hypertext Preprocessor (PHP) for a server side scripting language. For the first time, an Open Source scripting solution had passed Microsoft's proprietary Active Server Pages scripting to claim the top spot on the Netcraft survey. For both the April and May Netcraft surveys, PHP and ASP were almost too close to call, with Microsoft's product offering coming in just a hair under 24 percent of all hosts running a server-side script

PHP Code Review Guidelines

General  The code works  The code is easy to understand  Follows coding conventions  Names are simple and if possible short  Names are spelt correctly  Names contain units where applicable  There are no usages of magic numbers  No hard coded constants that could possibly change in the future  All variables are in the smallest scope possible  There is no commented out code  There is no dead code (inaccessible at Runtime)  No code that can be replaced with library functions  Variables are not accidentally used with null values  Variables are immutable where possible  Code is not repeated or duplicated  There is an else block for every if clause even if it is empty  No complex/long boolean expressions  No negatively named boolean variables  No empty blocks of code  Ideal data structures are used  Constructors do not accept null/none values  Catch clauses are fine grained and catch specific exceptions  Exceptions are not eaten if caught, unless explicitly documente

Multiple Checkboxes Validation

This is a regular problem and solution is hardly available on google. When we use multiple checkboxes of same group (same name with square brackets) and you've to read the values from server side, then below solution will help you. Here I'm using jQuery (https://jquery.com/) and jQuery Validate plugin (https://jqueryvalidation.org/) For an example, I've to ask a user which of the listed book they are interested to know about <form id="BooksForm" method="post" name="BooksForm"> <p>Books you are interested in </p> <input class="Books" id="Book1" name="Books[]" type="checkbox" value="1" /> The Inner Game of Tennis <br /> <input class="Books" id="Book2" name="Books[]" type="checkbox" value="1" /> Monk who sold his ferrari <br /> <input class="Books" id="Book3" name=&quo