My (bad) experience with Namespaces

Not for 'how-to' coding questions but PHP theory instead, this forum is here for those of us who wish to learn about design aspects of programming with PHP.

Moderator: General Moderators

Post Reply
User avatar
Chris Corbyn
Breakbeat Nuttzer
Posts: 13098
Joined: Wed Mar 24, 2004 7:57 am
Location: Melbourne, Australia

My (bad) experience with Namespaces

Post by Chris Corbyn »

My enthusiasm for namespace support in PHP has hit an all time low. Since grabbing a PHP 5.3 snapshot and trying to convert some of my existing code to use the namespaces I've come to realise just how much of a mess it all is 8O

Take the following (not real-world case) PHP 5.2:

Code: Select all

<?php

require_once 'MyApp/Page.php';

interface MyApp_PageReader
{
  public function readPage(MyApp_Page $page);
} 

Code: Select all

<?php

interface MyApp_Page
{
  public function getContents();
} 

Code: Select all

<?php

require_once 'MyApp/PageReader.php';
require_once 'MyApp/Page.php';

class MyApp_PageReader_SimplePageReader implements MyApp_PageReader
{
  public function readPage(MyApp_Page $page)
  {
    $something = $page->getContents();
  }
} 
And a Unit Test:

Code: Select all

<?php

require_once 'unit_tester.php';
require_once 'mock_objects.php';
require_once 'reporter.php';

require_once 'MyApp/PageReader/SimplePageReader.php';
require_once 'MyApp/Page.php';

Mock::Generate('MyApp_Page', 'MyApp_MockPage');

class MyApp_PageReader_SimplePageReaderTest extends UnitTestCase
{
  public function testPageIsRead()
  {
    $page = new MyApp_MockPage();
    $page->expectOnce('getContents');
    $reader = new MyApp_PageReader_SimplePageReader();
    $reader->readPage($page);
  }
}

$test = new MyApp_PageReader_SimplePageReaderTest();
$test->run(new HtmlReporter()); 
Now let's see the PHP 5.3 if we choose to use namepsaces:

Code: Select all

<?php

namespace MyApp;

require_once 'MyApp/Page.php';

interface PageReader
{
  public function readPage(Page $page);
} 

Code: Select all

<?php

namespace MyApp;

interface Page
{
  public function getContents();
} 

Code: Select all

<?php

namespace MyApp::PageReader;

require_once 'MyApp/PageReader.php';
require_once 'MyApp/Page.php';

use MyApp::PageReader;
use MyApp::Page;

class SimplePageReader implements PageReader
{
  public function readPage(Page $page)
  {
    $something = $page->getContents();
  }
} 
And a Unit Test:

Code: Select all

<?php

namespace MyApp::PageReader;

require_once 'unit_tester.php';
require_once 'mock_objects.php';
require_once 'reporter.php';

require_once 'MyApp/PageReader/SimplePageReader.php';
require_once 'MyApp/Page.php';

use MyApp::Page;
use ::UnitTestCase;
use ::Mock;
use ::HtmlReporter;

Mock::Generate('Page', 'MockPage'); //eval'd code doesn't get put in this namespace!!

class SimplePageReaderTest extends UnitTestCase
{
  public function testPageIsRead()
  {
    $page = new ::MockPage(); //Can't see any way around putting the :: prefix on this eval()'d code
    $page->expectOnce('getContents');
    $reader = new SimplePageReader();
    $reader->readPage($page);
  }
}

$test = new SimplePageReaderTest();
$test->run(new HtmlReporter()); 
* Working with 3rd-party code which doesn't use namespaces becomes a nuisance since you either need to import the classes explicitly or refer to all classes as "::className".
* Having to require() all files, then additionally import them feels pointless.
* Accessing globals has become incredibly expensive!
README.namespaces wrote:Names inside namespace are resolved according to the following rules:

1) all qualified names are translated during compilation according to
current import rules. So if we have "use A::B::C" and then "C::D::e()"
it is translated to "A::B::C::D::e()".
2) unqualified class names translated during compilation according to
current import rules. So if we have "use A::B::C" and then "new C()" it
is translated to "new A::B::C()".
3) inside namespace, calls to unqualified functions that are defined in
current namespace (and are known at the time the call is parsed) are
interpreted as calls to these namespace functions.
4) inside namespace, calls to unqualified functions that are not defined
in current namespace are resolved at run-time. The call to function foo()
inside namespace (A::B) first tries to find and call function from current
namespace A::B::foo() and if it doesn't exist PHP tries to call internal
function foo(). Note that using foo() inside namespace you can call only
internal PHP functions, however using ::foo() you are able to call any
function from the global namespace.
5) unqualified class names are resolved at run-time. E.q. "new Exception()"
first tries to use (and autoload) class from current namespace and in case
of failure uses internal PHP class. Note that using "new A" in namespace
you can only create class from this namespace or internal PHP class, however
using "new ::A" you are able to create any class from the global namespace.
6) Calls to qualified functions are resolved at run-time. Call to
A::B::foo() first tries to call function foo() from namespace A::B, then
it tries to find class A::B (__autoload() it if necessary) and call its
static method foo()
7) qualified class names are interpreted as class from corresponding
namespace. So "new A::B::C()" refers to class C from namespace A::B.

Examples
--------
<?php
namespace A;
foo(); // first tries to call "foo" defined in namespace "A"
// then calls internal function "foo"
::foo(); // calls function "foo" defined in global scope
?>

<?php
namespace A;
new B(); // first tries to create object of class "B" defined in namespace "A"
// then creates object of internal class "B"
new ::B(); // creates object of class "B" defined in global scope
?>

<?php
namespace A;
new A(); // first tries to create object of class "A" from namespace "A" (A::A)
// then creates object of internal class "A"
?>

<?php
namespace A;
B::foo(); // first tries to call function "foo" from namespace "A::B"
// then calls method "foo" of internal class "B"
::B::foo(); // first tries to call function "foo" from namespace "B"
// then calls method "foo" of class "B" from global scope
?>

The worst case if class name conflicts with namespace name
<?php
namespace A;
A::foo(); // first tries to call function "foo" from namespace "A::A"
// then tries to call method "foo" of class "A" from namespace "A"
// then tries to call function "foo" from namespace "A"
// then calls method "foo" of internal class "A"
::A::foo(); // first tries to call function "foo" from namespace "A"
// then calls method "foo" of class "A" from global scope
?>
There are some many opportunities for clashes (static method Foo::bar() when what you actually wanted was to run function foo() in namespace bar etc).

I can see so much dirty code coming out of this. I'm not sure I'll be adopting this new "feature" :(
User avatar
Christopher
Site Administrator
Posts: 13596
Joined: Wed Aug 25, 2004 7:54 pm
Location: New York, NY, US

Post by Christopher »

Yeah, but the people who have been clamoring for it like how all those extra lines make PHP look like a big boy language -- so complicated and impressive. I can't wait for an application server and those whatchamacallits that Ruby and Python have...
(#10850)
User avatar
Maugrim_The_Reaper
DevNet Master
Posts: 2704
Joined: Tue Nov 02, 2004 5:43 am
Location: Ireland

Post by Maugrim_The_Reaper »

I'm moving away from require_once which might help...a little. I'm using autoloading to some effect to reduce all the require_* crud which is also the direction PEAR2 is moving (and which is where Greg Beaver is planning to implement namespaces). I think namespaces will take a while to settle into a set of best practices. At the moment converting any random library will be messy since they're all designed around the non-namespace paradigm. Maybe namespaces are a good time to look into whether some refactoring would help? Not like namespaced code needs to worry about backwards compatibility as much.
Post Reply