PHP Namespace Mindfuck

folder_openPHP, Technology
comment7 Comments

I love PHP, I really do… just, sometimes you fall into these situations where you just can’t help bang your head against the table until even the table starts bleeding.

I’m currently working on a blog series about intelligent caching with PHP. During the preparation for this I had to create a framework which could demonstrate the caching concepts I was going to be discussing, and what better than using namespaces for it. Namespaces makes auto loading easy as pie, they give you an incredible freedom in class naming due to the extra encapsulation layer and in general they’re just brilliant. Except for this.

$controller = '\Evil\Controller\News';
$data = $controller::dataKeyInvalidates($invalidate);

It simply wouldn’t work. I was certain it worked fine without namespaces so for kicks I tried this:

$data = \Evil\Controller\News::dataKeyInvalidates($invalidate);

And it ran without problems. Fun. What eventually let me figure out what was going on was by echoing the variable passed to my auto loader. When I used a variable to reference the class name the auto loader was passed “\Evil\Controller\News” when I didn’t the auto loader was passed “Evil\Controller\News”. Obviously some magic goes on inside PHP that translates \Evil\Controller\News into “Evil\Controller\News”. Setting $controller to ‘Evil\Controller\News’ made it work perfectly fine.

So to recap this:

// Does not work.
$controller = '\Evil\Controller\News';
$data = $controller::dataKeyInvalidates($invalidate); 

// Works.
$controller = 'Evil\Controller\News';
$data = $controller::dataKeyInvalidates($invalidate); 

// Works.
$data = \Evil\Controller\News::dataKeyInvalidates($invalidate); 

// Does not work.
$data = Evil\Controller\News::dataKeyInvalidates($invalidate);

Related Posts

7 Comments. Leave new

  • Hi great article!

    But it seems to work for me:

    namespace Evil\Controller {
    
        class News {
            function dataKeyInvalidates($foo) {
                var_dump( 'WORKS', $foo); 
            }   
        }   
    }
    
    namespace {
    
        // Does not work.
        $controller = '\Evil\Controller\News';
        $data = $controller::dataKeyInvalidates(1); 
    
        // Works.
        $controller = 'Evil\Controller\News';
        $data = $controller::dataKeyInvalidates(2); 
    
        // Works.
        $data = \Evil\Controller\News::dataKeyInvalidates(3); 
    
        // Does not work.
        $data = Evil\Controller\News::dataKeyInvalidates(4);
    }
    

    output:

    string(5) “WORKS”
    int(1)
    string(5) “WORKS”
    int(2)
    string(5) “WORKS”
    int(3)
    string(5) “WORKS”
    int(4)

    PHP 5.3.2-1ubuntu4.2 with Suhosin-Patch (cli) (built: May 13 2010 20:03:45)

    Which version do you use?

    Reply
    • This was tested with PHP 5.3.3.

      The situation is not exactly the same, though. If you define your second namespace as anything other than the root you will get failed calls. But that is to be expected.

      The problem here was in the data passed to the autoloader. So to test this you’ll need multiple files.

      The interesting part in the following code is the data passed to the auto loader. Also note that you can only test one action at a time as once the file is loaded the remaining calls won’t call the autoloader.

      File 1:

      namespace Evil\Controller {
       
          class News {
              function dataKeyInvalidates($foo) {
                  // Nothing required
              }   
          }   
      }
      

      File 2

      namespace Evil\Core {
          class CacheController {
              public function __construct {
                  spl_autoload_register(array($this, 'autoLoadController'));
      
                  // Does not work.
                  $controller = '\Evil\Controller\News';
                  $data = $controller::dataKeyInvalidates(1); 
       
                  // Works.
                  $controller = 'Evil\Controller\News';
                  $data = $controller::dataKeyInvalidates(2); 
       
                  // Works.
                  $data = \Evil\Controller\News::dataKeyInvalidates(3); 
       
                  // Does not work.
                  $data = Evil\Controller\News::dataKeyInvalidates(4);
              }
      
              public function autoLoadController($class) {
                  var_dump($class);
              }
          }
      }
      
      Reply
  • Sorry for the delayed response, just following links from the NginX wiki…

    Even now PHP is an infant in the world of OOP let alone the encapsulation concepts born from its ideology. Although the authors who write their parts in C++ know this concept well when your trying to add it to what started out as nothing more then a template language there are gonna be things like this.

    Languages or their extensions in C’s case need to decide from the get go what they are going to be or changes to that later on will be a rough undertaking and this is one side effect.

    Examples are abound. Ruby/Python, both, Objective-C, just OOP, no namespaces, C++/Java/C#, just OOP, namespaces. PHP, uhh… template… uhh.. no wait, functional, uhhh… no, scap that, OOP, uhhh…. wait, OOP & namespaces.

    See? 😉

    Reply
  • It isn’t an escape problem ?


    $ php -r "var_dump('\Evil\Namespace');"
    ilNamespace');"string(15) "\Evil\Namespace"
    $ php -r "var_dump('\\Evil\\Namespace');"
    string(15) "\Evil\Namespace"

    Reply
    • I somehow doubt it. The issue is that when you use namespaces in their vanilla version you must use a \ first to specify that you want to start from the root namespace and not relatively to the current namespace. When you use namespaces through a variable you have to specify them in the style that’d normally be used for relative namespace paths.

      Even if it was an escaping problem then that does not explain why, what would normally be a relative namespace path (no prefixed \), would be treated as an absolute namespace path. (prefixed \)

      Reply
      • Somehow I have different outputs with PHP 5.2 and 5.3.5:


        $ php -v
        PHP 5.3.5 with Suhosin-Patch (cli) (built: Jan 6 2011 07:17:24)
        Copyright (c) 1997-2009 The PHP Group
        Zend Engine v2.3.0, Copyright (c) 1998-2010 Zend Technologies
        with Xdebug v2.1.0, Copyright (c) 2002-2010, by Derick Rethans
        $ php -r "var_dump('\Evil\Namespace');"
        ilNamespace');"string(15) "\Evil\Namespace"


        $ php -v
        PHP 5.3.3-1ubuntu9.3 with Suhosin-Patch (cli) (built: Jan 12 2011 16:07:38)
        Copyright (c) 1997-2009 The PHP Group
        Zend Engine v2.3.0, Copyright (c) 1998-2010 Zend Technologies
        with Xdebug v2.1.0, Copyright (c) 2002-2010, by Derick Rethans
        $ php -r "var_dump('\Evil\Namespace');"
        string(15) "\Evil\Namespace"

        Reply
        • It doesn’t make a lot of sense to just var_dump a string, at that point there’s nothing related to namespaces about it. It’s only interesting when used in combination with auto loaded class and namespaces.

          Reply

Leave a Reply

Your email address will not be published. Required fields are marked *

Fill out this field
Fill out this field
Please enter a valid email address.