My (bad) experience with Namespaces
Posted: Tue Jan 08, 2008 9:16 pm
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
Take the following (not real-world case) PHP 5.2:
And a Unit Test:
Now let's see the PHP 5.3 if we choose to use namepsaces:
And a Unit Test:
* 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!
I can see so much dirty code coming out of this. I'm not sure I'll be adopting this new "feature"
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();
}
} 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()); 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();
}
} 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()); * Having to require() all files, then additionally import them feels pointless.
* Accessing globals has become incredibly expensive!
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).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
?>
I can see so much dirty code coming out of this. I'm not sure I'll be adopting this new "feature"