分享

Leveraging PHP V5.3 namespaces for readable and maintainable code

 sumi2005 2014-07-13
IBM Bluemix. IBM's go-to-cloud platform. Join the beta.

"Conan is my role model." If I make that statement at the dinner table, my son would immediately think that I pattern myself after Conan the Barbarian, whereas my wife would think I want to be like the late-night talk show host, Conan O'Brien. This context confusion is known in IT as name collision. Many languages have a strategy to circumvent name collision and, with V5.3, so does PHP. PHP solves the name collision problem with its new namespaces feature. Of course, the names on which PHP resolves collision are not the names of people but rather the names of classes, functions, and constants.

This article explains why you should consider using namespaces on your next project. It provides an overview of namespace semantics, provides best practices, and offers a sample Model-View-Controller (MVC) application that uses namespaces. The article then discusses namespace support in Eclipse, NetBeans, and Zend Studio, with specific instructions on using namespaces with Eclipse.

Do I need namespaces?

A strength of the PHP language is its simplicity. So if you are new to PHP, namespaces are yet another concept you will need to understand. But if any of the following are true, you should consider their use:

  • You are developing a large application with hundreds of PHP files.
  • Your application is being developed by a team of coders.
  • You are planning on using frameworks that use PHP V5.3 and namespaces.
  • You have used namespaces (or comparable functionality, such as packages) in other languages, such as the Java?, Ruby, or Python languages.

If you are the sole developer of relatively small applications, namespaces may not be for you. But for the rest of us, namespaces provides a clean way to organize class structures and, of course, prevent name collision. These two reasons are why many framework developers are adopting the use of namespaces. Zend Framework (the 800-pound gorilla of PHP frameworks), for example, is using namespaces in Zend Framework V2.0.

Back to top

A quick overview

A namespace provides a context for a name. For example, the two classes shown in Listing 1 have name collision.

Listing 1. Two classes with the same name cause collision without namespaces
class Conan {
	var $bodyBuild = "extremely muscular";
	var $birthDate = 'before history';
	var $skill = 'fighting';
}

class Conan {
	var $bodyBuild = "very skinny";
	var $birthDate = '1963';
	var $skill = 'comedy';
}

To specify a namespace, you simply add a namespace declaration as the first statement in the source, as shown in Listing 2.

Listing 2. Two classes of the same name but with namespaces resolves collision
<?php
namespace barbarian;
class Conan {
	var $bodyBuild = "extremely muscular";
	var $birthDate = 'before history';
	var $skill = 'fighting';
}
namespace obrien;
class Conan {
	var $bodyBuild = "very skinny";
	var $birthDate = '1963';
	var $skill = 'comedy';
}
$conan = new \barbarian\Conan();
assert('extremely muscular born: before history' == 
   "$conan->bodyBuild born: $conan->birthDate");

$conan = new \obrien\Conan();
assert('very skinny born: 1963' == "$conan->bodyBuild born: $conan->birthDate");
?>

The above code runs fine, but before I describe why the two Conans work well together, let me point out two things. First, I'm using assertions to prove that the code works as expected. And second, I'm doing something you should never do: declaring multiple namespaces in one source file.

The namespace provides a unique qualifier for the two Conans. The code clearly states when I'm referring to the burly destroyer or the late-night talk show host. Notice that the syntax for the instantiation uses a backslash (\) followed by the namespace name:

$conan = new \barbarian\Conan();

and:

$conan = new \obrien\Conan();

Those qualifiers kind of look like Windows?-style directory qualifiers, which is not a bad way to think about them because, for one, namespaces support both relative and absolute references (just like directories), and for another, it is a best practice to put the source for your class files in directories that match the namespaces.

Back to top

Using namespaces

It would be more real-world to separate the two Conan classes into directories called barbarian and obrien, and then reference those classes from other PHP files. There are three ways to reference a PHP namespace:

  • Prefix the class name with the namespace
  • Import the namespace
  • Alias the namespace

To use the first option, you simply prefix the class name with the namespace (after, of course, you the include the source file):

include "barbarian/Conan.php";
$conan = new \barbarian\Conan();

That's pretty straightforward, but the issue with option one's strategy is that, given a large application, you will be constantly retyping the namespace. And besides all that typing, you are needlessly cluttering up your code base. With option two, you import the namespace with the PHP V5.3 reserved word use:

include "barbarian/Conan.php";
use barbarian\Conan;  
$conan = new Conan();

Option three lets you specify an alias for the namespace:

include "barbarian/Conan.php";
use \barbarian\Conan as Cimmerian;
$conan = new Cimmerian();

(Cimmerian, by the way, is yet another moniker Conan the Barbarian is known by.)

One issue I have with all three of the above examples is the use of the include statement. You can remove the need for the includes by using an __autoload function. The PHP magic method __autoload function is called whenever a class is referenced that has not yet been included in the source file. Place the code in Listing 3 in a file called autoload.php.

Listing 3. A magic __autoload function dynamically includes source files
<?php
function __autoload($classname) {
  $classname = ltrim($classname, '\\');
  $filename  = '';
  $namespace = '';
  if ($lastnspos = strripos($classname, '\\')) {
    $namespace = substr($classname, 0, $lastnspos);
    $classname = substr($classname, $lastnspos + 1);
    $filename  = str_replace('\\', '/', $namespace) . '/';
  }
  $filename .= str_replace('_', '/', $classname) . '.php';
  require $filename;
}
?>

Then import autoload.php into your source:

require_once "autoload.php"; 
use \barbarian\Conan as Cimmerian;

The big advantage of the auto-loader is that you won't have to create an include statement for every class. Note that although PHP's namespaces can be used for functions and constants as well as classes, the auto-loader technique only works for classes. The auto-loader is so handy that rather than coding functions, you can create methods in a appropriately named utility class and put your constants in immutable classes.

Back to top

Getting real with MVC

Leaving O'Brien to ridicule The Destroyer while being slain, let's move on to a simple example MVC application. To benefit from namespaces, you should design your naming conventions before keying a line of code. A common best practice is to use a namespace tree. Understand that namespaces have high-level namespaces and sub-namespaces. If your company has multiple applications, it might be handy to have a high-level namespace that is your company name. Then, you would use a sub-namespace for the application. Next, you'd have a level that contains directories that in turn have names that specify the application functionality of the PHP classes contained therein. For example, let's say the high-level company namespace is denoncourt, the first sub-level is retail, and the third level has functional names, as shown in Listing 4.

Listing 4. A design for namespaces can include nested sub-namespaces
/denoncourt
	/retail
		/common
		/controller
		/model
		/utility
		/view

The controller, model, and view sub-namespaces are obviously for the MVC architecture, but the utility and common sub-namespaces I threw in to be used for general classes that didn't cleanly fit in one of the other sub-namespaces.

Let's jump right into the code for the mini-MVC application. Listing 5 provides the code for index.php, which is placed in the root folder.

Listing 5. The MVC application's index PHP uses the controller class
<?php
require "autoload.php";
use denoncourt\retail\controller as Control;
$controller = new Control\Controller();
$controller->execute();
?>

Notice the long namespace and the use of the alias name of Control. The use of aliases is my preferred method for using namespaces for two reasons: First, if I later rename the namespace, I only have one line of code to change per source file. And second, given that it is a best practice to fully qualify your namespace as you instance classes, my use of Control\Controller() is effectively the same thing as \denoncourt\retail\controller\Controller(). Note that I could have just as well created an alias for a higher-level namespace, and then used the names of the sub-namespace for the class instantiation:

use denoncourt\retail as Retail;
$controller = new retail\controller\Controller();

This is a handy feature for those times when you will be referring to multiple levels of your namespace in the same source file. In the denoncourt/retail/controller directory, I created Controller.php, which is shown in Listing 6.

Listing 6. The MVC controller class predicates action based on user input
<?php
namespace denoncourt\retail\controller;
use denoncourt\retail as retail;

class Controller {
  public function execute() {
    switch ($_GET['action']) {
    case 'showItem' :
      $item = new retail\model\Item();
      require "denoncourt/retail/utils/format.php";
      require "denoncourt/retail/view/item.php";
      break;
    }
  }
}
?>

In denoncourt/retail/model, I created Item.php. Listing 7 shows the code.

Listing 7. The MVC Item class is in the model sub-namespace
<?php
namespace denoncourt\retail\model;
class Item {
  public $itemNo = '123';
  public $price = 2.45;
  public $qtyOnHand = 87;
}
?>

In denoncourt/retail/utils, I created format.php, which is shown Listing 8.

Listing 8. The dollar PHP shows how a function can also be namespaced
<?php
namespace denoncourt\retail;
function dollar($dollar) {
    return "\$$dollar";
}
?>

Note that, as stated earlier, I would have preferred to put the format function in a utility class (so the auto-loader would handle the import of the code and I wouldn't have had to code the require statement for format.php).

Finally, the item.php view page is in denoncourt/retail/views. Listing 9 shows the code.

Listing 9. The item page displays the model instanced in the controller
<html>
<head>
<style>
dt {
  float:left; clear:left;
  font-weight:bold;
  margin-right:10px;
  width:15%;
  text-align: right;
}
dd { text-align:left; }
</style>
</head>
<body>
<dl>
  <dt>Item No:</dt><dd><?php echo "$item->itemNo"; ?></dd>
  <dt>Price:</dt><dd>
       <?php echo \denoncourt\retail\dollar($item->price); ?>
       </dd>
  <dt>Quantity On Hand:</dt><dd><?php echo "$item->qtyOnHand"; ?></dd>
</dl>
</body>
</html>

Notice how the item page qualifies the dollar function with \denoncourt\retail\ namespace.

Back to top

Fall back

If a source file has a namespace declaration, then all references to classes, functions, and constants use the namespace semantics. When PHP encounters an unqualified class, function, or constant, it does what is know as fallback. A fallback on a user class causes the compiler to assume the current namespace. To refer to non-namespaced classes, you need to put a lone backslash. For example, to refer to PHP Exception class, you would use $error = new \Exception();. Keep that in mind as you use any of the Standard PHP Library classes (such as ArrayObject, FindFile, and KeyFilter).

For functions and constants, if the current namespace does not contain that function or constant, PHP's fallback mechanism will fall back to the standard PHP function. So, for example, if you've coded your own strlen function, PHP would resolve to your function. But, if you also wanted to use the standard PHP strlen function (say, within your own strlen implementation), you'd need to precede the function invocation with a backslash, as Listing 10 shows.

Listing 10. PHP standard functions can be qualified with a backslash to identify the global namespace
<?php
namespace denoncourt\retail;
function strlen($str) {
    return \strlen();
}
?>

Back to top

The namespace global variable and strings

If you like to code dynamic methods, you may be tempted to place a namespace in a double-quoted string: "denoncourt\retail\controller". But remember that you'll need to escape those slashes: "denoncourt\\retail\\controller". One workaround is simply to use single quotation marks: 'denoncourt\retail\controller'.

As you do your dynamic programming, keep in mind that PHP V5.3 has a new global variable called __NAMESPACE__. Consider using the global variable rather than typing it:

$echo 'I am using this namespace:'.__NAMESPACE__;

Back to top

IDE support for namespaces

Most of the major IDEs already have support for PHP V5.3. NetBeans V6.8 has great support for namespaces. Not only does it have code completion but it also makes suggestions for improving your code with best practices. For example, it is a best practice with PHP namespaces to fully qualify your namespaces within your code using with absolute references rather than relative references. If you key code that uses relative namespace qualifiers, NetBeans displays a light bulb icon in the left-most code margin. If you hover over the icon, NetBeans shows a tool tip describing the suggested change. And if you then click the icon, NetBeans makes the code change for you.

Zend Studio provides similar capabilities. If you are reticent to begin using namespaces, consider upgrading your IDE and try out namespaces with a bit of help from your favorite IDE. Note that you may find that you don't even have to upgrade your IDE, as many of them have provided PHP V5.3 features for more than a year now.

PHP Development Tools (PDT) V2.1 also has solid support for namespaces. PDT is a plug-in for Eclipse. A link to the installation notes for PDT is provided in the Resources section.

To enable namespace support, I first had to tell Eclipse/PDT to use PHP V5.3. To do that, from the application main menu, click Window > Preferences, as Figure 1 shows. Expand PHP in the tree pane, then choose PHP Interpreter. Then, change the PHP version to PHP 5.3, and click OK.

Figure 1. The Eclipse PDT plug-in requires you to set the interpreter to PHP V5.3
Set the interpreter to V5.3

You can create a PHP project by clicking File > New Project, expanding the PHP node, and then clicking PHP Project. To create a PHP file, simply right-click the project in PHP Explorer, then click PHP file. PDT uses appropriate syntax highlighting for the namespace keywords of namespace and use (see Figure 2).

Figure 2. PDT uses syntax highlighting for namespace keywords and displays namespaces in the PHP Explorer and Outline views
Image showing how PHP uses syntax highlighting

It's handy to have PDT show you the namespaces in the PHP Explorer and Outline views, as it helps you visualize how your namespaces are assigned to various classes. PDT also provides something we've come to expect with IDEs: code completion (see Figure 3). Code completion is invoked by PHP while keying the use statement.

Figure 3. PDT provides code completion for namespaces
Screenshot showing a list of namespace options for code completion in a code listing

PDT will also pop up a code completion window while you key class names. For example, if I type new Item, PDT will also show a window listing Item ? denoncourt\retail\item.

When I select denoncourt\retail\item, PDT inserts the required use statement and the qualifier on the instantiation line:

use denoncourt\retail\model;
new model\Item();

What's cool is when I type new Conan, PDT also shows a window listing:

	Conan ? obrien
	Conan ? barbarian

Allowing me to select the appropriate Conan. And now that I've wandered back to my infatuation with the two Conans, perhaps it is time to wrap things up.

Back to top

Wrapping up

If you are still hesitant to get started with namespaces, before you put off learning namespaces for another year, I suggest you load your favorite IDE with PHP V5.3 support and give namespaces a whirl. As to naming conventions, it's more important to set some simple ones than to agonize over coming up with the perfect strategy. Personally, with a long background in Java development, I like to follow the Java naming conventions. I use camel-cased names for my PHP namespaces and stay away from the underscores. By using namespaces in your next PHP project, your code will be cleaner and more organized. You will become acquainted with a facility that is common to most leading languages. And you will be prepared for using the wealth of frameworks already using PHP V5.3 — and namespaces in particular.

Back to top

Download

DescriptionNameSize
Sample scriptsos-php-5.3namespaces_code.zip6KB

Resources

Learn

Get products and technologies

Discuss

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多