Powered By Blogger

sábado, 6 de octubre de 2012

Namespacing in PHP

It’s been a bumpy ride, in regards to namespace support in PHP. Thankfully, it was added to the language in PHP 5.3, and the applicable structure of PHP code has improved greatly since then. But how exactly do we use them?


What’s a Namespace?

"Don’t forget to escape the backslash when you store a namespace name in a string!"

Imagine a namespace as a drawer in which you can put all kinds of things: a pencil, a ruler, a piece of paper and so forth. These are your belongings. Directly underneath your drawer is someone else’s, and he puts the same things in it. To avoid using each other’s items, you decide to label the drawers so it’s clear what belongs to whom.

Previously, developers had to use underscores in their classes, functions and constants to separate code bases. That’s equivalent to labeling each others belongings and putting them in one big drawer. Sure, it’s at least some kind of organization, but it’s very inefficient.

Namespacing to the rescue! You can declare the same function, class, interface and constant definitions in separate namespaces without receiving fatal errors. Essentialy, a namespace is nothing more than a hierarchically labeled code block holding regular PHP code.

You are Using Them!

It is important to keep in mind that you indirectly make use of namespaces; as of PHP 5.3, all the definitions which are not yet declared in a user defined namespace fall under the global namespace.

The global namespace also holds all internal PHP definitions, like echo(), mysqli_connect(), and the Exception class. Because the global namespace has no unique identifying name, its most often referred to as the global space.

Note that it’s not an obligation to use namespacing.

Your PHP script will work perfectly fine without them, and this behavior isn’t about to change very soon.


Defining a Namespace

A namespace definition is the first statement the PHP interpreter should encounter in a PHP file. The only statement allowed to occur above a namespace declaration is a declare statement, and then again, only if it declares the encoding of the script.

Declaring a namespace is as simple as using the namespace keyword. A namespace name should obey the same rules as other identifiers in PHP. Therefore, a namespace must start with a letter or underscore, followed by any number of letters, numbers, or underscores.

  <?php  namespace MyProject {      // Regular PHP code goes here, anything goes!      function run()      {          echo 'Running from a namespace!';      }  }  

If you want to assign a code block to the global space, you use the namespace keyword without appending a name.

  <?php  namespace {      // Global space!  }  

You are allowed to have multiple namespaces in the same file.

  <?php  namespace MyProject {  }  namespace MySecondProject {  }  namespace {  }  

You can also scatter the same namespace throughout different files; the process of file inclusion automatically merges them. Therefore, it’s a good coding practice to limit the amount of namespace definitions to one per file, just like you would do with classes.

Namespacing is used to avoid conflicting definitions and introduce more flexibility and organization in your code base.

Note that the brackets surrounding the namespace code block are completely optional. In fact, sticking to the one-namespace-per-file rule and omitting the curly brackets makes your code a lot cleaner–there’s no need to indent the nested code.

Sub-namespaces

Namespaces can follow a certain hierarchy, much like the directories in the file system on your computer. Sub-namespaces are extremely useful for organizing the structure of a project. For example, if your project requires database access, you might want to put all the database-related code, such as a database exception and connection handler, in a sub-namespace called Database.

To maintain flexibility, it is wise to store sub-namespaces in sub-directories. This encourages structuring of your project and makes it much easier to use autoloaders that follow the PSR-0 standard.

PHP uses the backslash as its namespace separator.


Fun fact: in the RFC to decide which namespace separator should be used, they even considered using a smiley.
  // myproject/database/connection.php  <?php  namespace MyProject\Database  class Connection {      // Handling database connections  }  

You can have as many sub-namespaces as you want.

  <?php  namespace MyProject\Blog\Auth\Handler\Social;  class Twitter {      // Handles Twitter authentification  }  

Defining sub-namespaces with nested code blocks is not supported. The following example will throw a very descriptive fatal error: “Namespace declarations cannot be nested”.

  <?php  namespace MyProject {      namespace Database {          class Connection { }      }  }  

Calling Code from a Namespace

If you want to instantiate a new object, call a function or use a constant from a different namespace, you use the backslash notation. They can be resolved from three different view points:

  • Unqualified name
  • Qualified name
  • Fully qualified name

Unqualified Name

This is the name of a class, function or constant without including a reference to any namespace whatsoever. If you are new to namespacing, this is the view point you are used to working from.

  <?php  namespace MyProject;  class MyClass {      static function static_method()      {          echo 'Hello, world!';      }  }  // Unqualified name, resolves to the namespace you are currently in (MyProject\MyClass)  MyClass:static_method();  

Qualified Name

This is how we access the sub-namespace hierarchy; it makes use of the backslash notation.

  <?php  namespace MyProject;  require 'myproject/database/connection.php';  // Qualified name, instantiating a class from a sub-namespace of MyProject  $connection = new Database\Connection();  

The example below throws a fatal error: “Fatal error: Class ‘MyProject\Database\MyProject\FileAccess\Input’ not found” because MyProject\FileAccess\Input is approached relatively to the namespace you are currently in.

  <?php  namespace MyProject\Database;  require 'myproject/fileaccess/input.php';  // Trying to access the MyProject\FileAccess\Input class  $input = new MyProject\FileAccess\Input();  

Fully Qualified Name

The unqualified and qualified names are both relative to the namespace you are currently in. They can only be used to access definitions on that level or to dive deeper into the namespace hierarchy.

If you want to access a function, class or constant residing at a higher level in the hierarchy, then you need to use the fully qualified name–an absolute path rather than relative. This boils down to prepending your call with a backslash. This lets PHP know that this call should be resolved from the global space instead of approaching it relatively.

  <?php  namespace MyProject\Database;  require 'myproject/fileaccess/input.php';  // Trying to access the MyProject\FileAccess\Input class  // This time it will work because we use the fully qualified name, note the leading backslash  $input = new \MyProject\FileAccess\Input();  

It’s not required to use the fully qualified name of internal PHP functions. Calling an unqualified name for a constant or function which does not exist in the namespace you are currently working in results in PHP searching the global namespace for them. This is a built-in fallback which does not apply to unqualified class names.

With this in mind, we can now overload internal PHP functions whilst still being able to call the original function (or constant for that matter).

  <?php  namespace MyProject;  var_dump($query); // Overloaded  \var_dump($query); // Internal  // We want to access the global Exception class  // The following will not work because there's no class called Exception in the MyProject\Database namespace and unqualified class names do not have a fallback to global space  // throw new Exception('Query failed!');  // Instead, we use a single backslash to indicate we want to resolve from global space  throw new \Exception('ailed!');  function var_dump() {      echo 'Overloaded global var_dump()!<br />';  }  

Dynamic calls

PHP is a dynamic programming language; so you can also apply this functionality for calling namespaced code. This is essentially the same as instantiating variable classes or including variable files. The namespace separator PHP uses is also a meta character in strings. Don’t forget to escape the backslash when you store a namespace name in a string!

  <?php  namespace OtherProject;  $project_name = 'MyProject';  $package_name = 'Database';  $class_name = 'Connection';  // Include a variable file  require strtolower($project_name . '/'. $package_name .  '/' . $class_name) . '.php';  // Name of a variable class in a variable namespace. Note how the backslash is escaped to use it properly  $fully_qualified_name = $project_name . '\\' . $package_name . '\\' . $class_name;  $connection = new $fully_qualified_name();  

The namespace Keyword

Not only is the namespace keyword used to define a namespace, it can also be used to explicitly resolve to the current namespace, functionally similar to the self keyword for classes.

  <?php  namespace MyProject;  function run()  {      echo 'Running from a namespace!';  }  // Resolves to MyProject\run  run();  // Explicitly resolves to MyProject\run  namespace\run();  

The __NAMESPACE__ constant

Much like the self keyword cannot be used to determine what the current class name is, the namespace keyword cannot be used to determine what the current namespace is. This is why we have the __NAMESPACE__ constant.

  <?php  namespace MyProject\Database;  // 'MyProject\Database'  echo __NAMESPACE__;  

This constant is very useful for learning if you are just starting out with namespaces; it is also helpful for debugging. As it is a string, it can also be used in combination with dynamic code calls which we previously discussed.


Aliasing or Importing

"it’s not an obligation to use namespacing"

Namespacing in PHP has support for importing. Importing is also referred to as aliasing. Only classes, interfaces, and namespaces can be aliased or imported.

Importing is a very useful and fundamental aspect of namespacing. It gives you the ability to make use of external code packages, like libraries, without having to worry about conflicting names. Importing is achieved by using the use keyword. Optionally, you can specify a custom alias with the as keyword.

  use [name of class, interface or namespace] as [optional_custom_alias]  

How it’s Done

A fully qualified name can be aliased to a shorter unqualified name so that you don’t have to write its fully qualified name each time you want to make use of it. Aliasing or importing should occur in the highest scope of a namespace or in the global scope. Trying to do this in the scope of a method or function is invalid syntax.

  <?php  namespace OtherProject;  // This holds the MyProject\Database namespace with a Connection class in it  require 'myproject/database/connection.php';  // If we want to access the database connection of MyProject, we need to use its fully qualified name as we're in a different name space  $connection = new \MyProject\Database\Connection();  // Import the Connection class (it works exactly the same with interfaces)  use MyProject\Database\Connection;  // Now this works too! Before the Connection class was aliased PHP would not have found an OtherProject\Connection class  $connection = new Connection();  // Import the MyProject\Database namespace  use MyProject\Database;  $connection = new Database\Connection()  

Alternatively, you can alias to it a different name:

  <?php  namespace OtherProject;  require 'myproject/database/connection.php';  use MyProject\Database\Connection as MyConnection;  $connection = new MyConnection();  use MyProject\Database as MyDatabase;  $connection = new MyDatabase\Connection();  

You are also allowed to import global classes, like the Exception class. When imported, you don’t have to write its fully qualified name anymore.

Note that import names are not resolved as relative to the current namespace but from an absolute standpoint, starting at the global space. This means that a leading backslash is unnecessary and not recommended.

  <?php  namespace MyProject;  // Fatal error: Class 'SomeProject\Exception' not found  throw new Exception('An exception!');  // OK!  throw new \Exception('An exception!');  // Import global Exception. 'Exception' is resolved from an absolute standpoint, the leading backslash is unnecessary  use Exception;  // OK!  throw new Exception('An exception!');  

Though it is possible to dynamically call namespaced code, dynamic importing is not supported.

  <?php  namespace OtherProject;  $parser = 'markdown';  // This is valid PHP  require 'myproject/blog/parser/' . $parser . '.php';  // This is not  use MyProject\Blog\Parser\$parser;  

Conclusion

Namespacing is used to avoid conflicting definitions and introduce more flexibility and organization in your code base. Remember that you are not obligated to use namespacing; it’s a feature used in combination with an object oriented workflow. Hopefully, however, you will consider taking your (future) PHP project to the next level by making use of namespacing. Have you decided yet?



No hay comentarios: